1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * 4 * Zimbra Collaboration Suite Web Client 5 * Copyright (C) 2012 VMware, Inc. 6 * 7 * The contents of this file are subject to the Zimbra Public License 8 * Version 1.3 ("License"); you may not use this file except in 9 * compliance with the License. You may obtain a copy of the License at 10 * http://www.zimbra.com/license. 11 * 12 * Software distributed under the License is distributed on an "AS IS" 13 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. 14 * 15 * ***** END LICENSE BLOCK ***** 16 */ 17 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY 18 (function(win) { 19 var whiteSpaceRe = /^\s*|\s*$/g, 20 undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1'; 21 22 var tinymce = { 23 majorVersion : '3', 24 25 minorVersion : '5.4.1', 26 27 releaseDate : '2012-06-24', 28 29 _init : function() { 30 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; 31 32 t.isOpera = win.opera && opera.buildNumber; 33 34 t.isWebKit = /WebKit/.test(ua); 35 36 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName); 37 38 t.isIE6 = t.isIE && /MSIE [56]/.test(ua); 39 40 t.isIE7 = t.isIE && /MSIE [7]/.test(ua); 41 42 t.isIE8 = t.isIE && /MSIE [8]/.test(ua); 43 44 t.isIE9 = t.isIE && /MSIE [9]/.test(ua); 45 46 t.isGecko = !t.isWebKit && /Gecko/.test(ua); 47 48 t.isMac = ua.indexOf('Mac') != -1; 49 50 t.isAir = /adobeair/i.test(ua); 51 52 t.isIDevice = /(iPad|iPhone)/.test(ua); 53 54 t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534; 55 56 // TinyMCE .NET webcontrol might be setting the values for TinyMCE 57 if (win.tinyMCEPreInit) { 58 t.suffix = tinyMCEPreInit.suffix; 59 t.baseURL = tinyMCEPreInit.base; 60 t.query = tinyMCEPreInit.query; 61 return; 62 } 63 64 // Get suffix and base 65 t.suffix = ''; 66 67 // If base element found, add that infront of baseURL 68 nl = d.getElementsByTagName('base'); 69 for (i=0; i<nl.length; i++) { 70 v = nl[i].href; 71 if (v) { 72 // Host only value like http://site.com or http://site.com:8008 73 if (/^https?:\/\/[^\/]+$/.test(v)) 74 v += '/'; 75 76 base = v ? v.match(/.*\//)[0] : ''; // Get only directory 77 } 78 } 79 80 function getBase(n) { 81 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) { 82 if (/_(src|dev)\.js/g.test(n.src)) 83 t.suffix = '_src'; 84 85 if ((p = n.src.indexOf('?')) != -1) 86 t.query = n.src.substring(p + 1); 87 88 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/')); 89 90 // If path to script is relative and a base href was found add that one infront 91 // the src property will always be an absolute one on non IE browsers and IE 8 92 // so this logic will basically only be executed on older IE versions 93 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0) 94 t.baseURL = base + t.baseURL; 95 96 return t.baseURL; 97 } 98 99 return null; 100 }; 101 102 // Check document 103 nl = d.getElementsByTagName('script'); 104 for (i=0; i<nl.length; i++) { 105 if (getBase(nl[i])) 106 return; 107 } 108 109 // Check head 110 n = d.getElementsByTagName('head')[0]; 111 if (n) { 112 nl = n.getElementsByTagName('script'); 113 for (i=0; i<nl.length; i++) { 114 if (getBase(nl[i])) 115 return; 116 } 117 } 118 119 return; 120 }, 121 122 is : function(o, t) { 123 if (!t) 124 return o !== undef; 125 126 if (t == 'array' && (o.hasOwnProperty && o instanceof Array)) 127 return true; 128 129 return typeof(o) == t; 130 }, 131 132 makeMap : function(items, delim, map) { 133 var i; 134 135 items = items || []; 136 delim = delim || ','; 137 138 if (typeof(items) == "string") 139 items = items.split(delim); 140 141 map = map || {}; 142 143 i = items.length; 144 while (i--) 145 map[items[i]] = {}; 146 147 return map; 148 }, 149 150 each : function(o, cb, s) { 151 var n, l; 152 153 if (!o) 154 return 0; 155 156 s = s || o; 157 158 if (o.length !== undef) { 159 // Indexed arrays, needed for Safari 160 for (n=0, l = o.length; n < l; n++) { 161 if (cb.call(s, o[n], n, o) === false) 162 return 0; 163 } 164 } else { 165 // Hashtables 166 for (n in o) { 167 if (o.hasOwnProperty(n)) { 168 if (cb.call(s, o[n], n, o) === false) 169 return 0; 170 } 171 } 172 } 173 174 return 1; 175 }, 176 177 178 map : function(a, f) { 179 var o = []; 180 181 tinymce.each(a, function(v) { 182 o.push(f(v)); 183 }); 184 185 return o; 186 }, 187 188 grep : function(a, f) { 189 var o = []; 190 191 tinymce.each(a, function(v) { 192 if (!f || f(v)) 193 o.push(v); 194 }); 195 196 return o; 197 }, 198 199 inArray : function(a, v) { 200 var i, l; 201 202 if (a) { 203 for (i = 0, l = a.length; i < l; i++) { 204 if (a[i] === v) 205 return i; 206 } 207 } 208 209 return -1; 210 }, 211 212 extend : function(obj, ext) { 213 var i, l, name, args = arguments, value; 214 215 for (i = 1, l = args.length; i < l; i++) { 216 ext = args[i]; 217 for (name in ext) { 218 if (ext.hasOwnProperty(name)) { 219 value = ext[name]; 220 221 if (value !== undef) { 222 obj[name] = value; 223 } 224 } 225 } 226 } 227 228 return obj; 229 }, 230 231 232 trim : function(s) { 233 return (s ? '' + s : '').replace(whiteSpaceRe, ''); 234 }, 235 236 create : function(s, p, root) { 237 var t = this, sp, ns, cn, scn, c, de = 0; 238 239 // Parse : <prefix> <class>:<super class> 240 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); 241 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name 242 243 // Create namespace for new class 244 ns = t.createNS(s[3].replace(/\.\w+$/, ''), root); 245 246 // Class already exists 247 if (ns[cn]) 248 return; 249 250 // Make pure static class 251 if (s[2] == 'static') { 252 ns[cn] = p; 253 254 if (this.onCreate) 255 this.onCreate(s[2], s[3], ns[cn]); 256 257 return; 258 } 259 260 // Create default constructor 261 if (!p[cn]) { 262 p[cn] = function() {}; 263 de = 1; 264 } 265 266 // Add constructor and methods 267 ns[cn] = p[cn]; 268 t.extend(ns[cn].prototype, p); 269 270 // Extend 271 if (s[5]) { 272 sp = t.resolve(s[5]).prototype; 273 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name 274 275 // Extend constructor 276 c = ns[cn]; 277 if (de) { 278 // Add passthrough constructor 279 ns[cn] = function() { 280 return sp[scn].apply(this, arguments); 281 }; 282 } else { 283 // Add inherit constructor 284 ns[cn] = function() { 285 this.parent = sp[scn]; 286 return c.apply(this, arguments); 287 }; 288 } 289 ns[cn].prototype[cn] = ns[cn]; 290 291 // Add super methods 292 t.each(sp, function(f, n) { 293 ns[cn].prototype[n] = sp[n]; 294 }); 295 296 // Add overridden methods 297 t.each(p, function(f, n) { 298 // Extend methods if needed 299 if (sp[n]) { 300 ns[cn].prototype[n] = function() { 301 this.parent = sp[n]; 302 return f.apply(this, arguments); 303 }; 304 } else { 305 if (n != cn) 306 ns[cn].prototype[n] = f; 307 } 308 }); 309 } 310 311 // Add static methods 312 t.each(p['static'], function(f, n) { 313 ns[cn][n] = f; 314 }); 315 316 if (this.onCreate) 317 this.onCreate(s[2], s[3], ns[cn].prototype); 318 }, 319 320 walk : function(o, f, n, s) { 321 s = s || this; 322 323 if (o) { 324 if (n) 325 o = o[n]; 326 327 tinymce.each(o, function(o, i) { 328 if (f.call(s, o, i, n) === false) 329 return false; 330 331 tinymce.walk(o, f, n, s); 332 }); 333 } 334 }, 335 336 createNS : function(n, o) { 337 var i, v; 338 339 o = o || win; 340 341 n = n.split('.'); 342 for (i=0; i<n.length; i++) { 343 v = n[i]; 344 345 if (!o[v]) 346 o[v] = {}; 347 348 o = o[v]; 349 } 350 351 return o; 352 }, 353 354 resolve : function(n, o) { 355 var i, l; 356 357 o = o || win; 358 359 n = n.split('.'); 360 for (i = 0, l = n.length; i < l; i++) { 361 o = o[n[i]]; 362 363 if (!o) 364 break; 365 } 366 367 return o; 368 }, 369 370 addUnload : function(f, s) { 371 var t = this, unload; 372 373 unload = function() { 374 var li = t.unloads, o, n; 375 376 if (li) { 377 // Call unload handlers 378 for (n in li) { 379 o = li[n]; 380 381 if (o && o.func) 382 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy 383 } 384 385 // Detach unload function 386 if (win.detachEvent) { 387 win.detachEvent('onbeforeunload', fakeUnload); 388 win.detachEvent('onunload', unload); 389 } else if (win.removeEventListener) 390 win.removeEventListener('unload', unload, false); 391 392 // Destroy references 393 t.unloads = o = li = w = unload = 0; 394 395 // Run garbarge collector on IE 396 if (win.CollectGarbage) 397 CollectGarbage(); 398 } 399 }; 400 401 function fakeUnload() { 402 var d = document; 403 404 function stop() { 405 // Prevent memory leak 406 d.detachEvent('onstop', stop); 407 408 // Call unload handler 409 if (unload) 410 unload(); 411 412 d = 0; 413 }; 414 415 // Is there things still loading, then do some magic 416 if (d.readyState == 'interactive') { 417 // Fire unload when the currently loading page is stopped 418 if (d) 419 d.attachEvent('onstop', stop); 420 421 // Remove onstop listener after a while to prevent the unload function 422 // to execute if the user presses cancel in an onbeforeunload 423 // confirm dialog and then presses the browser stop button 424 win.setTimeout(function() { 425 if (d) 426 d.detachEvent('onstop', stop); 427 }, 0); 428 } 429 }; 430 431 f = {func : f, scope : s || this}; 432 433 if (!t.unloads) { 434 // Attach unload handler 435 if (win.attachEvent) { 436 win.attachEvent('onunload', unload); 437 win.attachEvent('onbeforeunload', fakeUnload); 438 } else if (win.addEventListener) 439 win.addEventListener('unload', unload, false); 440 441 // Setup initial unload handler array 442 t.unloads = [f]; 443 } else 444 t.unloads.push(f); 445 446 return f; 447 }, 448 449 removeUnload : function(f) { 450 var u = this.unloads, r = null; 451 452 tinymce.each(u, function(o, i) { 453 if (o && o.func == f) { 454 u.splice(i, 1); 455 r = f; 456 return false; 457 } 458 }); 459 460 return r; 461 }, 462 463 explode : function(s, d) { 464 if (!s || tinymce.is(s, 'array')) { 465 return s; 466 } 467 468 return tinymce.map(s.split(d || ','), tinymce.trim); 469 }, 470 471 _addVer : function(u) { 472 var v; 473 474 if (!this.query) 475 return u; 476 477 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query; 478 479 if (u.indexOf('#') == -1) 480 return u + v; 481 482 return u.replace('#', v + '#'); 483 }, 484 485 // Fix function for IE 9 where regexps isn't working correctly 486 // Todo: remove me once MS fixes the bug 487 _replace : function(find, replace, str) { 488 // On IE9 we have to fake $x replacement 489 if (isRegExpBroken) { 490 return str.replace(find, function() { 491 var val = replace, args = arguments, i; 492 493 for (i = 0; i < args.length - 2; i++) { 494 if (args[i] === undef) { 495 val = val.replace(new RegExp('\\$' + i, 'g'), ''); 496 } else { 497 val = val.replace(new RegExp('\\$' + i, 'g'), args[i]); 498 } 499 } 500 501 return val; 502 }); 503 } 504 505 return str.replace(find, replace); 506 } 507 508 }; 509 510 // Initialize the API 511 tinymce._init(); 512 513 // Expose tinymce namespace to the global namespace (window) 514 win.tinymce = win.tinyMCE = tinymce; 515 516 // Describe the different namespaces 517 518 })(window); 519 520 521 522 (function() { 523 if (!window.Prototype) 524 return alert("Load prototype first!"); 525 526 // Patch in core NS functions 527 tinymce.extend(tinymce, { 528 trim : function(s) {return s ? s.strip() : '';}, 529 inArray : function(a, v) {return a && a.indexOf ? a.indexOf(v) : -1;} 530 }); 531 532 // Patch in functions in various clases 533 // Add a "#ifndefjquery" statement around each core API function you add below 534 var patches = { 535 'tinymce.util.JSON' : { 536 /*serialize : function(o) { 537 return o.toJSON(); 538 }*/ 539 } 540 }; 541 542 // Patch functions after a class is created 543 tinymce.onCreate = function(ty, c, p) { 544 tinymce.extend(p, patches[c]); 545 }; 546 })(); 547 548 549 tinymce.create('tinymce.util.Dispatcher', { 550 scope : null, 551 listeners : null, 552 inDispatch: false, 553 554 Dispatcher : function(scope) { 555 this.scope = scope || this; 556 this.listeners = []; 557 }, 558 559 add : function(callback, scope) { 560 this.listeners.push({cb : callback, scope : scope || this.scope}); 561 562 return callback; 563 }, 564 565 addToTop : function(callback, scope) { 566 var self = this, listener = {cb : callback, scope : scope || self.scope}; 567 568 // Create new listeners if addToTop is executed in a dispatch loop 569 if (self.inDispatch) { 570 self.listeners = [listener].concat(self.listeners); 571 } else { 572 self.listeners.unshift(listener); 573 } 574 575 return callback; 576 }, 577 578 remove : function(callback) { 579 var listeners = this.listeners, output = null; 580 581 tinymce.each(listeners, function(listener, i) { 582 if (callback == listener.cb) { 583 output = listener; 584 listeners.splice(i, 1); 585 return false; 586 } 587 }); 588 589 return output; 590 }, 591 592 dispatch : function() { 593 var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener; 594 595 self.inDispatch = true; 596 597 // Needs to be a real loop since the listener count might change while looping 598 // And this is also more efficient 599 for (i = 0; i < listeners.length; i++) { 600 listener = listeners[i]; 601 returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]); 602 603 if (returnValue === false) 604 break; 605 } 606 607 self.inDispatch = false; 608 609 return returnValue; 610 } 611 612 }); 613 614 (function() { 615 var each = tinymce.each; 616 617 tinymce.create('tinymce.util.URI', { 618 URI : function(u, s) { 619 var t = this, o, a, b, base_url; 620 621 // Trim whitespace 622 u = tinymce.trim(u); 623 624 // Default settings 625 s = t.settings = s || {}; 626 627 // Strange app protocol that isn't http/https or local anchor 628 // For example: mailto,skype,tel etc. 629 if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) { 630 t.source = u; 631 return; 632 } 633 634 // Absolute path with no host, fake host and protocol 635 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0) 636 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u; 637 638 // Relative path http:// or protocol relative //path 639 if (!/^[\w\-]*:?\/\//.test(u)) { 640 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory; 641 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u); 642 } 643 644 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) 645 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something 646 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u); 647 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) { 648 var s = u[i]; 649 650 // Zope 3 workaround, they use @@something 651 if (s) 652 s = s.replace(/\(mce_at\)/g, '@@'); 653 654 t[v] = s; 655 }); 656 657 b = s.base_uri; 658 if (b) { 659 if (!t.protocol) 660 t.protocol = b.protocol; 661 662 if (!t.userInfo) 663 t.userInfo = b.userInfo; 664 665 if (!t.port && t.host === 'mce_host') 666 t.port = b.port; 667 668 if (!t.host || t.host === 'mce_host') 669 t.host = b.host; 670 671 t.source = ''; 672 } 673 674 //t.path = t.path || '/'; 675 }, 676 677 setPath : function(p) { 678 var t = this; 679 680 p = /^(.*?)\/?(\w+)?$/.exec(p); 681 682 // Update path parts 683 t.path = p[0]; 684 t.directory = p[1]; 685 t.file = p[2]; 686 687 // Rebuild source 688 t.source = ''; 689 t.getURI(); 690 }, 691 692 toRelative : function(u) { 693 var t = this, o; 694 695 if (u === "./") 696 return u; 697 698 u = new tinymce.util.URI(u, {base_uri : t}); 699 700 // Not on same domain/port or protocol 701 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol) 702 return u.getURI(); 703 704 var tu = t.getURI(), uu = u.getURI(); 705 706 // Allow usage of the base_uri when relative_urls = true 707 if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) 708 return tu; 709 710 o = t.toRelPath(t.path, u.path); 711 712 // Add query 713 if (u.query) 714 o += '?' + u.query; 715 716 // Add anchor 717 if (u.anchor) 718 o += '#' + u.anchor; 719 720 return o; 721 }, 722 723 toAbsolute : function(u, nh) { 724 u = new tinymce.util.URI(u, {base_uri : this}); 725 726 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0); 727 }, 728 729 toRelPath : function(base, path) { 730 var items, bp = 0, out = '', i, l; 731 732 // Split the paths 733 base = base.substring(0, base.lastIndexOf('/')); 734 base = base.split('/'); 735 items = path.split('/'); 736 737 if (base.length >= items.length) { 738 for (i = 0, l = base.length; i < l; i++) { 739 if (i >= items.length || base[i] != items[i]) { 740 bp = i + 1; 741 break; 742 } 743 } 744 } 745 746 if (base.length < items.length) { 747 for (i = 0, l = items.length; i < l; i++) { 748 if (i >= base.length || base[i] != items[i]) { 749 bp = i + 1; 750 break; 751 } 752 } 753 } 754 755 if (bp === 1) 756 return path; 757 758 for (i = 0, l = base.length - (bp - 1); i < l; i++) 759 out += "../"; 760 761 for (i = bp - 1, l = items.length; i < l; i++) { 762 if (i != bp - 1) 763 out += "/" + items[i]; 764 else 765 out += items[i]; 766 } 767 768 return out; 769 }, 770 771 toAbsPath : function(base, path) { 772 var i, nb = 0, o = [], tr, outPath; 773 774 // Split paths 775 tr = /\/$/.test(path) ? '/' : ''; 776 base = base.split('/'); 777 path = path.split('/'); 778 779 // Remove empty chunks 780 each(base, function(k) { 781 if (k) 782 o.push(k); 783 }); 784 785 base = o; 786 787 // Merge relURLParts chunks 788 for (i = path.length - 1, o = []; i >= 0; i--) { 789 // Ignore empty or . 790 if (path[i].length === 0 || path[i] === ".") 791 continue; 792 793 // Is parent 794 if (path[i] === '..') { 795 nb++; 796 continue; 797 } 798 799 // Move up 800 if (nb > 0) { 801 nb--; 802 continue; 803 } 804 805 o.push(path[i]); 806 } 807 808 i = base.length - nb; 809 810 // If /a/b/c or / 811 if (i <= 0) 812 outPath = o.reverse().join('/'); 813 else 814 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); 815 816 // Add front / if it's needed 817 if (outPath.indexOf('/') !== 0) 818 outPath = '/' + outPath; 819 820 // Add traling / if it's needed 821 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) 822 outPath += tr; 823 824 return outPath; 825 }, 826 827 getURI : function(nh) { 828 var s, t = this; 829 830 // Rebuild source 831 if (!t.source || nh) { 832 s = ''; 833 834 if (!nh) { 835 if (t.protocol) 836 s += t.protocol + '://'; 837 838 if (t.userInfo) 839 s += t.userInfo + '@'; 840 841 if (t.host) 842 s += t.host; 843 844 if (t.port) 845 s += ':' + t.port; 846 } 847 848 if (t.path) 849 s += t.path; 850 851 if (t.query) 852 s += '?' + t.query; 853 854 if (t.anchor) 855 s += '#' + t.anchor; 856 857 t.source = s; 858 } 859 860 return t.source; 861 } 862 }); 863 })(); 864 865 (function() { 866 var each = tinymce.each; 867 868 tinymce.create('static tinymce.util.Cookie', { 869 getHash : function(n) { 870 var v = this.get(n), h; 871 872 if (v) { 873 each(v.split('&'), function(v) { 874 v = v.split('='); 875 h = h || {}; 876 h[unescape(v[0])] = unescape(v[1]); 877 }); 878 } 879 880 return h; 881 }, 882 883 setHash : function(n, v, e, p, d, s) { 884 var o = ''; 885 886 each(v, function(v, k) { 887 o += (!o ? '' : '&') + escape(k) + '=' + escape(v); 888 }); 889 890 this.set(n, o, e, p, d, s); 891 }, 892 893 get : function(n) { 894 var c = document.cookie, e, p = n + "=", b; 895 896 // Strict mode 897 if (!c) 898 return; 899 900 b = c.indexOf("; " + p); 901 902 if (b == -1) { 903 b = c.indexOf(p); 904 905 if (b !== 0) 906 return null; 907 } else 908 b += 2; 909 910 e = c.indexOf(";", b); 911 912 if (e == -1) 913 e = c.length; 914 915 return unescape(c.substring(b + p.length, e)); 916 }, 917 918 set : function(n, v, e, p, d, s) { 919 document.cookie = n + "=" + escape(v) + 920 ((e) ? "; expires=" + e.toGMTString() : "") + 921 ((p) ? "; path=" + escape(p) : "") + 922 ((d) ? "; domain=" + d : "") + 923 ((s) ? "; secure" : ""); 924 }, 925 926 remove : function(name, path, domain) { 927 var date = new Date(); 928 929 date.setTime(date.getTime() - 1000); 930 931 this.set(name, '', date, path, domain); 932 } 933 }); 934 })(); 935 936 (function() { 937 function serialize(o, quote) { 938 var i, v, t, name; 939 940 quote = quote || '"'; 941 942 if (o == null) 943 return 'null'; 944 945 t = typeof o; 946 947 if (t == 'string') { 948 v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; 949 950 return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { 951 // Make sure single quotes never get encoded inside double quotes for JSON compatibility 952 if (quote === '"' && a === "'") 953 return a; 954 955 i = v.indexOf(b); 956 957 if (i + 1) 958 return '\\' + v.charAt(i + 1); 959 960 a = b.charCodeAt().toString(16); 961 962 return '\\u' + '0000'.substring(a.length) + a; 963 }) + quote; 964 } 965 966 if (t == 'object') { 967 if (o.hasOwnProperty && o instanceof Array) { 968 for (i=0, v = '['; i<o.length; i++) 969 v += (i > 0 ? ',' : '') + serialize(o[i], quote); 970 971 return v + ']'; 972 } 973 974 v = '{'; 975 976 for (name in o) { 977 if (o.hasOwnProperty(name)) { 978 v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : ''; 979 } 980 } 981 982 return v + '}'; 983 } 984 985 return '' + o; 986 }; 987 988 tinymce.util.JSON = { 989 serialize: serialize, 990 991 parse: function(s) { 992 try { 993 return eval('(' + s + ')'); 994 } catch (ex) { 995 // Ignore 996 } 997 } 998 999 }; 1000 })(); 1001 1002 tinymce.create('static tinymce.util.XHR', { 1003 send : function(o) { 1004 var x, t, w = window, c = 0; 1005 1006 function ready() { 1007 if (!o.async || x.readyState == 4 || c++ > 10000) { 1008 if (o.success && c < 10000 && x.status == 200) 1009 o.success.call(o.success_scope, '' + x.responseText, x, o); 1010 else if (o.error) 1011 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o); 1012 1013 x = null; 1014 } else 1015 w.setTimeout(ready, 10); 1016 }; 1017 1018 // Default settings 1019 o.scope = o.scope || this; 1020 o.success_scope = o.success_scope || o.scope; 1021 o.error_scope = o.error_scope || o.scope; 1022 o.async = o.async === false ? false : true; 1023 o.data = o.data || ''; 1024 1025 function get(s) { 1026 x = 0; 1027 1028 try { 1029 x = new ActiveXObject(s); 1030 } catch (ex) { 1031 } 1032 1033 return x; 1034 }; 1035 1036 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP'); 1037 1038 if (x) { 1039 if (x.overrideMimeType) 1040 x.overrideMimeType(o.content_type); 1041 1042 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async); 1043 1044 if (o.content_type) 1045 x.setRequestHeader('Content-Type', o.content_type); 1046 1047 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 1048 1049 x.send(o.data); 1050 1051 // Syncronous request 1052 if (!o.async) 1053 return ready(); 1054 1055 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE 1056 t = w.setTimeout(ready, 10); 1057 } 1058 } 1059 }); 1060 1061 (function() { 1062 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR; 1063 1064 tinymce.create('tinymce.util.JSONRequest', { 1065 JSONRequest : function(s) { 1066 this.settings = extend({ 1067 }, s); 1068 this.count = 0; 1069 }, 1070 1071 send : function(o) { 1072 var ecb = o.error, scb = o.success; 1073 1074 o = extend(this.settings, o); 1075 1076 o.success = function(c, x) { 1077 c = JSON.parse(c); 1078 1079 if (typeof(c) == 'undefined') { 1080 c = { 1081 error : 'JSON Parse error.' 1082 }; 1083 } 1084 1085 if (c.error) 1086 ecb.call(o.error_scope || o.scope, c.error, x); 1087 else 1088 scb.call(o.success_scope || o.scope, c.result); 1089 }; 1090 1091 o.error = function(ty, x) { 1092 if (ecb) 1093 ecb.call(o.error_scope || o.scope, ty, x); 1094 }; 1095 1096 o.data = JSON.serialize({ 1097 id : o.id || 'c' + (this.count++), 1098 method : o.method, 1099 params : o.params 1100 }); 1101 1102 // JSON content type for Ruby on rails. Bug: #1883287 1103 o.content_type = 'application/json'; 1104 1105 XHR.send(o); 1106 }, 1107 1108 'static' : { 1109 sendRPC : function(o) { 1110 return new tinymce.util.JSONRequest().send(o); 1111 } 1112 } 1113 }); 1114 }()); 1115 (function(tinymce){ 1116 tinymce.VK = { 1117 BACKSPACE: 8, 1118 DELETE: 46, 1119 DOWN: 40, 1120 ENTER: 13, 1121 LEFT: 37, 1122 RIGHT: 39, 1123 SPACEBAR: 32, 1124 TAB: 9, 1125 UP: 38, 1126 1127 modifierPressed: function (e) { 1128 return e.shiftKey || e.ctrlKey || e.altKey; 1129 }, 1130 1131 metaKeyPressed: function(e) { 1132 return tinymce.isMac ? e.metaKey : e.ctrlKey; 1133 } 1134 }; 1135 })(tinymce); 1136 1137 tinymce.util.Quirks = function(editor) { 1138 var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings; 1139 1140 function setEditorCommandState(cmd, state) { 1141 try { 1142 editor.getDoc().execCommand(cmd, false, state); 1143 } catch (ex) { 1144 // Ignore 1145 } 1146 } 1147 1148 function getDocumentMode() { 1149 var documentMode = editor.getDoc().documentMode; 1150 1151 return documentMode ? documentMode : 6; 1152 }; 1153 1154 function cleanupStylesWhenDeleting() { 1155 function removeMergedFormatSpans(isDelete) { 1156 var rng, blockElm, node, clonedSpan; 1157 1158 rng = selection.getRng(); 1159 1160 // Find root block 1161 blockElm = dom.getParent(rng.startContainer, dom.isBlock); 1162 1163 // On delete clone the root span of the next block element 1164 if (isDelete) 1165 blockElm = dom.getNext(blockElm, dom.isBlock); 1166 1167 // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace 1168 if (blockElm) { 1169 node = blockElm.firstChild; 1170 1171 // Ignore empty text nodes 1172 while (node && node.nodeType == 3 && node.nodeValue.length === 0) 1173 node = node.nextSibling; 1174 1175 if (node && node.nodeName === 'SPAN') { 1176 clonedSpan = node.cloneNode(false); 1177 } 1178 } 1179 1180 // Do the backspace/delete action 1181 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); 1182 1183 // Find all odd apple-style-spans 1184 blockElm = dom.getParent(rng.startContainer, dom.isBlock); 1185 tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) { 1186 var bm = selection.getBookmark(); 1187 1188 if (clonedSpan) { 1189 dom.replace(clonedSpan.cloneNode(false), span, true); 1190 } else { 1191 dom.remove(span, true); 1192 } 1193 1194 // Restore the selection 1195 selection.moveToBookmark(bm); 1196 }); 1197 }; 1198 1199 editor.onKeyDown.add(function(editor, e) { 1200 var isDelete; 1201 1202 isDelete = e.keyCode == DELETE; 1203 if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { 1204 e.preventDefault(); 1205 removeMergedFormatSpans(isDelete); 1206 } 1207 }); 1208 1209 editor.addCommand('Delete', function() {removeMergedFormatSpans();}); 1210 }; 1211 1212 function emptyEditorWhenDeleting() { 1213 function serializeRng(rng) { 1214 var body = dom.create("body"); 1215 var contents = rng.cloneContents(); 1216 body.appendChild(contents); 1217 return selection.serializer.serialize(body, {format: 'html'}); 1218 } 1219 1220 function allContentsSelected(rng) { 1221 var selection = serializeRng(rng); 1222 1223 var allRng = dom.createRng(); 1224 allRng.selectNode(editor.getBody()); 1225 1226 var allSelection = serializeRng(allRng);//console.log(selection, "----", allSelection); 1227 return selection === allSelection; 1228 } 1229 1230 editor.onKeyDown.add(function(editor, e) { 1231 var keyCode = e.keyCode, isCollapsed; 1232 1233 // Empty the editor if it's needed for example backspace at <p><b>|</b></p> 1234 if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) { 1235 isCollapsed = editor.selection.isCollapsed(); 1236 1237 // Selection is collapsed but the editor isn't empty 1238 if (isCollapsed && !dom.isEmpty(editor.getBody())) { 1239 return; 1240 } 1241 1242 // IE deletes all contents correctly when everything is selected 1243 if (tinymce.isIE && !isCollapsed) { 1244 return; 1245 } 1246 1247 // Selection isn't collapsed but not all the contents is selected 1248 if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) { 1249 return; 1250 } 1251 1252 // Manually empty the editor 1253 editor.setContent(''); 1254 editor.selection.setCursorLocation(editor.getBody(), 0); 1255 editor.nodeChanged(); 1256 } 1257 }); 1258 }; 1259 1260 function selectAll() { 1261 editor.onKeyDown.add(function(editor, e) { 1262 if (e.keyCode == 65 && VK.metaKeyPressed(e)) { 1263 e.preventDefault(); 1264 editor.execCommand('SelectAll'); 1265 } 1266 }); 1267 }; 1268 1269 function inputMethodFocus() { 1270 if (!editor.settings.content_editable) { 1271 // Case 1 IME doesn't initialize if you focus the document 1272 dom.bind(editor.getDoc(), 'focusin', function(e) { 1273 selection.setRng(selection.getRng()); 1274 }); 1275 1276 // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event 1277 dom.bind(editor.getDoc(), 'mousedown', function(e) { 1278 if (e.target == editor.getDoc().documentElement) { 1279 editor.getWin().focus(); 1280 selection.setRng(selection.getRng()); 1281 } 1282 }); 1283 } 1284 }; 1285 1286 function removeHrOnBackspace() { 1287 editor.onKeyDown.add(function(editor, e) { 1288 if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) { 1289 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 1290 var node = selection.getNode(); 1291 var previousSibling = node.previousSibling; 1292 1293 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { 1294 dom.remove(previousSibling); 1295 tinymce.dom.Event.cancel(e); 1296 } 1297 } 1298 } 1299 }) 1300 } 1301 1302 function focusBody() { 1303 // Fix for a focus bug in FF 3.x where the body element 1304 // wouldn't get proper focus if the user clicked on the HTML element 1305 if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 1306 editor.onMouseDown.add(function(editor, e) { 1307 if (e.target.nodeName === "HTML") { 1308 var body = editor.getBody(); 1309 1310 // Blur the body it's focused but not correctly focused 1311 body.blur(); 1312 1313 // Refocus the body after a little while 1314 setTimeout(function() { 1315 body.focus(); 1316 }, 0); 1317 } 1318 }); 1319 } 1320 }; 1321 1322 function selectControlElements() { 1323 editor.onClick.add(function(editor, e) { 1324 e = e.target; 1325 1326 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 1327 // WebKit can't even do simple things like selecting an image 1328 // Needs tobe the setBaseAndExtend or it will fail to select floated images 1329 if (/^(IMG|HR)$/.test(e.nodeName)) { 1330 selection.getSel().setBaseAndExtent(e, 0, e, 1); 1331 } 1332 1333 if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) { 1334 selection.select(e); 1335 } 1336 1337 editor.nodeChanged(); 1338 }); 1339 }; 1340 1341 function removeStylesWhenDeletingAccrossBlockElements() { 1342 function getAttributeApplyFunction() { 1343 var template = dom.getAttribs(selection.getStart().cloneNode(false)); 1344 1345 return function() { 1346 var target = selection.getStart(); 1347 1348 if (target !== editor.getBody()) { 1349 dom.setAttrib(target, "style", null); 1350 1351 tinymce.each(template, function(attr) { 1352 target.setAttributeNode(attr.cloneNode(true)); 1353 }); 1354 } 1355 }; 1356 } 1357 1358 function isSelectionAcrossElements() { 1359 return !selection.isCollapsed() && selection.getStart() != selection.getEnd(); 1360 } 1361 1362 function blockEvent(editor, e) { 1363 e.preventDefault(); 1364 return false; 1365 } 1366 1367 editor.onKeyPress.add(function(editor, e) { 1368 var applyAttributes; 1369 1370 if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { 1371 applyAttributes = getAttributeApplyFunction(); 1372 editor.getDoc().execCommand('delete', false, null); 1373 applyAttributes(); 1374 e.preventDefault(); 1375 return false; 1376 } 1377 }); 1378 1379 dom.bind(editor.getDoc(), 'cut', function(e) { 1380 var applyAttributes; 1381 1382 if (isSelectionAcrossElements()) { 1383 applyAttributes = getAttributeApplyFunction(); 1384 editor.onKeyUp.addToTop(blockEvent); 1385 1386 setTimeout(function() { 1387 applyAttributes(); 1388 editor.onKeyUp.remove(blockEvent); 1389 }, 0); 1390 } 1391 }); 1392 } 1393 1394 function selectionChangeNodeChanged() { 1395 var lastRng, selectionTimer; 1396 1397 dom.bind(editor.getDoc(), 'selectionchange', function() { 1398 if (selectionTimer) { 1399 clearTimeout(selectionTimer); 1400 selectionTimer = 0; 1401 } 1402 1403 selectionTimer = window.setTimeout(function() { 1404 var rng = selection.getRng(); 1405 1406 // Compare the ranges to see if it was a real change or not 1407 if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) { 1408 editor.nodeChanged(); 1409 lastRng = rng; 1410 } 1411 }, 50); 1412 }); 1413 } 1414 1415 function ensureBodyHasRoleApplication() { 1416 document.body.setAttribute("role", "application"); 1417 } 1418 1419 function disableBackspaceIntoATable() { 1420 editor.onKeyDown.add(function(editor, e) { 1421 if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) { 1422 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 1423 var previousSibling = selection.getNode().previousSibling; 1424 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { 1425 return tinymce.dom.Event.cancel(e); 1426 } 1427 } 1428 } 1429 }) 1430 } 1431 1432 function addNewLinesBeforeBrInPre() { 1433 // IE8+ rendering mode does the right thing with BR in PRE 1434 if (getDocumentMode() > 7) { 1435 return; 1436 } 1437 1438 // Enable display: none in area and add a specific class that hides all BR elements in PRE to 1439 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key 1440 setEditorCommandState('RespectVisibilityInDesign', true); 1441 editor.contentStyles.push('.mceHideBrInPre pre br {display: none}'); 1442 dom.addClass(editor.getBody(), 'mceHideBrInPre'); 1443 1444 // Adds a \n before all BR elements in PRE to get them visual 1445 editor.parser.addNodeFilter('pre', function(nodes, name) { 1446 var i = nodes.length, brNodes, j, brElm, sibling; 1447 1448 while (i--) { 1449 brNodes = nodes[i].getAll('br'); 1450 j = brNodes.length; 1451 while (j--) { 1452 brElm = brNodes[j]; 1453 1454 // Add \n before BR in PRE elements on older IE:s so the new lines get rendered 1455 sibling = brElm.prev; 1456 if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') { 1457 sibling.value += '\n'; 1458 } else { 1459 brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n'; 1460 } 1461 } 1462 } 1463 }); 1464 1465 // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible 1466 editor.serializer.addNodeFilter('pre', function(nodes, name) { 1467 var i = nodes.length, brNodes, j, brElm, sibling; 1468 1469 while (i--) { 1470 brNodes = nodes[i].getAll('br'); 1471 j = brNodes.length; 1472 while (j--) { 1473 brElm = brNodes[j]; 1474 sibling = brElm.prev; 1475 if (sibling && sibling.type == 3) { 1476 sibling.value = sibling.value.replace(/\r?\n$/, ''); 1477 } 1478 } 1479 } 1480 }); 1481 } 1482 1483 function removePreSerializedStylesWhenSelectingControls() { 1484 dom.bind(editor.getBody(), 'mouseup', function(e) { 1485 var value, node = selection.getNode(); 1486 1487 // Moved styles to attributes on IMG eements 1488 if (node.nodeName == 'IMG') { 1489 // Convert style width to width attribute 1490 if (value = dom.getStyle(node, 'width')) { 1491 dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, '')); 1492 dom.setStyle(node, 'width', ''); 1493 } 1494 1495 // Convert style height to height attribute 1496 if (value = dom.getStyle(node, 'height')) { 1497 dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, '')); 1498 dom.setStyle(node, 'height', ''); 1499 } 1500 } 1501 }); 1502 } 1503 1504 function keepInlineElementOnDeleteBackspace() { 1505 editor.onKeyDown.add(function(editor, e) { 1506 var isDelete, rng, container, offset, brElm, sibling, collapsed; 1507 1508 isDelete = e.keyCode == DELETE; 1509 if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { 1510 rng = selection.getRng(); 1511 container = rng.startContainer; 1512 offset = rng.startOffset; 1513 collapsed = rng.collapsed; 1514 1515 // Override delete if the start container is a text node and is at the beginning of text or 1516 // just before/after the last character to be deleted in collapsed mode 1517 if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) { 1518 nonEmptyElements = editor.schema.getNonEmptyElements(); 1519 1520 // Prevent default logic since it's broken 1521 e.preventDefault(); 1522 1523 // Insert a BR before the text node this will prevent the containing element from being deleted/converted 1524 brElm = dom.create('br', {id: '__tmp'}); 1525 container.parentNode.insertBefore(brElm, container); 1526 1527 // Do the browser delete 1528 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); 1529 1530 // Check if the previous sibling is empty after deleting for example: <p><b></b>|</p> 1531 container = selection.getRng().startContainer; 1532 sibling = container.previousSibling; 1533 if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) { 1534 dom.remove(sibling); 1535 } 1536 1537 // Remove the temp element we inserted 1538 dom.remove('__tmp'); 1539 } 1540 } 1541 }); 1542 } 1543 1544 function removeBlockQuoteOnBackSpace() { 1545 // Add block quote deletion handler 1546 editor.onKeyDown.add(function(editor, e) { 1547 var rng, container, offset, root, parent; 1548 1549 if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) { 1550 return; 1551 } 1552 1553 rng = selection.getRng(); 1554 container = rng.startContainer; 1555 offset = rng.startOffset; 1556 root = dom.getRoot(); 1557 parent = container; 1558 1559 if (!rng.collapsed || offset !== 0) { 1560 return; 1561 } 1562 1563 while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) { 1564 parent = parent.parentNode; 1565 } 1566 1567 // Is the cursor at the beginning of a blockquote? 1568 if (parent.tagName === 'BLOCKQUOTE') { 1569 // Remove the blockquote 1570 editor.formatter.toggle('blockquote', null, parent); 1571 1572 // Move the caret to the beginning of container 1573 rng.setStart(container, 0); 1574 rng.setEnd(container, 0); 1575 selection.setRng(rng); 1576 selection.collapse(false); 1577 } 1578 }); 1579 }; 1580 1581 function setGeckoEditingOptions() { 1582 function setOpts() { 1583 editor._refreshContentEditable(); 1584 1585 setEditorCommandState("StyleWithCSS", false); 1586 setEditorCommandState("enableInlineTableEditing", false); 1587 1588 if (!settings.object_resizing) { 1589 setEditorCommandState("enableObjectResizing", false); 1590 } 1591 }; 1592 1593 if (!settings.readonly) { 1594 editor.onBeforeExecCommand.add(setOpts); 1595 editor.onMouseDown.add(setOpts); 1596 } 1597 }; 1598 1599 function addBrAfterLastLinks() { 1600 function fixLinks(editor, o) { 1601 tinymce.each(dom.select('a'), function(node) { 1602 var parentNode = node.parentNode, root = dom.getRoot(); 1603 1604 if (parentNode.lastChild === node) { 1605 while (parentNode && !dom.isBlock(parentNode)) { 1606 if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) { 1607 return; 1608 } 1609 1610 parentNode = parentNode.parentNode; 1611 } 1612 1613 dom.add(parentNode, 'br', {'data-mce-bogus' : 1}); 1614 } 1615 }); 1616 }; 1617 1618 editor.onExecCommand.add(function(editor, cmd) { 1619 if (cmd === 'CreateLink') { 1620 fixLinks(editor); 1621 } 1622 }); 1623 1624 editor.onSetContent.add(selection.onSetContent.add(fixLinks)); 1625 }; 1626 1627 function setDefaultBlockType() { 1628 if (settings.forced_root_block) { 1629 editor.onInit.add(function() { 1630 setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block); 1631 }); 1632 } 1633 } 1634 1635 function removeGhostSelection() { 1636 function repaint(sender, args) { 1637 if (!sender || !args.initial) { 1638 editor.execCommand('mceRepaint'); 1639 } 1640 }; 1641 1642 editor.onUndo.add(repaint); 1643 editor.onRedo.add(repaint); 1644 editor.onSetContent.add(repaint); 1645 }; 1646 1647 function deleteControlItemOnBackSpace() { 1648 editor.onKeyDown.add(function(editor, e) { 1649 var rng; 1650 1651 if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) { 1652 rng = editor.getDoc().selection.createRange(); 1653 if (rng && rng.item) { 1654 e.preventDefault(); 1655 editor.undoManager.beforeChange(); 1656 dom.remove(rng.item(0)); 1657 editor.undoManager.add(); 1658 } 1659 } 1660 }); 1661 }; 1662 1663 function renderEmptyBlocksFix() { 1664 var emptyBlocksCSS; 1665 1666 // IE10+ 1667 if (getDocumentMode() >= 10) { 1668 emptyBlocksCSS = ''; 1669 tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { 1670 emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; 1671 }); 1672 1673 editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}'); 1674 } 1675 }; 1676 1677 function fakeImageResize() { 1678 var mouseDownImg, startX, startY, startW, startH; 1679 1680 if (!settings.object_resizing || settings.webkit_fake_resize === false) { 1681 return; 1682 } 1683 1684 editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}'); 1685 1686 function resizeImage(e) { 1687 var deltaX, deltaY, ratio, width, height; 1688 1689 if (mouseDownImg) { 1690 deltaX = e.screenX - startX; 1691 deltaY = e.screenY - startY; 1692 ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH); 1693 1694 // Only update styles if the user draged one pixel or more 1695 if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) { 1696 // Constrain proportions 1697 width = Math.round(startW * ratio); 1698 height = Math.round(startH * ratio); 1699 1700 // Resize by using style or attribute 1701 if (mouseDownImg.style.width) { 1702 dom.setStyle(mouseDownImg, 'width', width); 1703 } else { 1704 dom.setAttrib(mouseDownImg, 'width', width); 1705 } 1706 1707 // Resize by using style or attribute 1708 if (mouseDownImg.style.height) { 1709 dom.setStyle(mouseDownImg, 'height', height); 1710 } else { 1711 dom.setAttrib(mouseDownImg, 'height', height); 1712 } 1713 1714 if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) { 1715 dom.addClass(editor.getBody(), 'mceResizeImages'); 1716 } 1717 } 1718 } 1719 }; 1720 1721 editor.onMouseDown.add(function(editor, e) { 1722 var target = e.target; 1723 1724 if (target.nodeName == "IMG") { 1725 mouseDownImg = target; 1726 startX = e.screenX; 1727 startY = e.screenY; 1728 startW = mouseDownImg.clientWidth; 1729 startH = mouseDownImg.clientHeight; 1730 dom.bind(editor.getDoc(), 'mousemove', resizeImage); 1731 e.preventDefault(); 1732 } 1733 }); 1734 1735 // Unbind events on node change and restore resize cursor 1736 editor.onNodeChange.add(function() { 1737 if (mouseDownImg) { 1738 mouseDownImg = null; 1739 dom.unbind(editor.getDoc(), 'mousemove', resizeImage); 1740 } 1741 1742 if (selection.getNode().nodeName == "IMG") { 1743 dom.addClass(editor.getBody(), 'mceResizeImages'); 1744 } else { 1745 dom.removeClass(editor.getBody(), 'mceResizeImages'); 1746 } 1747 }); 1748 }; 1749 1750 // All browsers 1751 disableBackspaceIntoATable(); 1752 removeBlockQuoteOnBackSpace(); 1753 emptyEditorWhenDeleting(); 1754 1755 // WebKit 1756 if (tinymce.isWebKit) { 1757 keepInlineElementOnDeleteBackspace(); 1758 cleanupStylesWhenDeleting(); 1759 inputMethodFocus(); 1760 selectControlElements(); 1761 setDefaultBlockType(); 1762 1763 // iOS 1764 if (tinymce.isIDevice) { 1765 selectionChangeNodeChanged(); 1766 } else { 1767 fakeImageResize(); 1768 selectAll(); 1769 } 1770 } 1771 1772 // IE 1773 if (tinymce.isIE) { 1774 removeHrOnBackspace(); 1775 ensureBodyHasRoleApplication(); 1776 addNewLinesBeforeBrInPre(); 1777 removePreSerializedStylesWhenSelectingControls(); 1778 deleteControlItemOnBackSpace(); 1779 renderEmptyBlocksFix(); 1780 } 1781 1782 // Gecko 1783 if (tinymce.isGecko) { 1784 removeHrOnBackspace(); 1785 focusBody(); 1786 removeStylesWhenDeletingAccrossBlockElements(); 1787 setGeckoEditingOptions(); 1788 addBrAfterLastLinks(); 1789 removeGhostSelection(); 1790 } 1791 }; 1792 (function(tinymce) { 1793 var namedEntities, baseEntities, reverseEntities, 1794 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 1795 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 1796 rawCharsRegExp = /[<>&\"\']/g, 1797 entityRegExp = /&(#x|#)?([\w]+);/g, 1798 asciiMap = { 1799 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020", 1800 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152", 1801 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022", 1802 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A", 1803 156 : "\u0153", 158 : "\u017E", 159 : "\u0178" 1804 }; 1805 1806 // Raw entities 1807 baseEntities = { 1808 '\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code 1809 "'" : ''', 1810 '<' : '<', 1811 '>' : '>', 1812 '&' : '&' 1813 }; 1814 1815 // Reverse lookup table for raw entities 1816 reverseEntities = { 1817 '<' : '<', 1818 '>' : '>', 1819 '&' : '&', 1820 '"' : '"', 1821 ''' : "'" 1822 }; 1823 1824 // Decodes text by using the browser 1825 function nativeDecode(text) { 1826 var elm; 1827 1828 elm = document.createElement("div"); 1829 elm.innerHTML = text; 1830 1831 return elm.textContent || elm.innerText || text; 1832 }; 1833 1834 // Build a two way lookup table for the entities 1835 function buildEntitiesLookup(items, radix) { 1836 var i, chr, entity, lookup = {}; 1837 1838 if (items) { 1839 items = items.split(','); 1840 radix = radix || 10; 1841 1842 // Build entities lookup table 1843 for (i = 0; i < items.length; i += 2) { 1844 chr = String.fromCharCode(parseInt(items[i], radix)); 1845 1846 // Only add non base entities 1847 if (!baseEntities[chr]) { 1848 entity = '&' + items[i + 1] + ';'; 1849 lookup[chr] = entity; 1850 lookup[entity] = chr; 1851 } 1852 } 1853 1854 return lookup; 1855 } 1856 }; 1857 1858 // Unpack entities lookup where the numbers are in radix 32 to reduce the size 1859 namedEntities = buildEntitiesLookup( 1860 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + 1861 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + 1862 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + 1863 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + 1864 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + 1865 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + 1866 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + 1867 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + 1868 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + 1869 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + 1870 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + 1871 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + 1872 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + 1873 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + 1874 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + 1875 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + 1876 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + 1877 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + 1878 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + 1879 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + 1880 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + 1881 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + 1882 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + 1883 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + 1884 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32); 1885 1886 tinymce.html = tinymce.html || {}; 1887 1888 tinymce.html.Entities = { 1889 encodeRaw : function(text, attr) { 1890 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1891 return baseEntities[chr] || chr; 1892 }); 1893 }, 1894 1895 encodeAllRaw : function(text) { 1896 return ('' + text).replace(rawCharsRegExp, function(chr) { 1897 return baseEntities[chr] || chr; 1898 }); 1899 }, 1900 1901 encodeNumeric : function(text, attr) { 1902 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1903 // Multi byte sequence convert it to a single entity 1904 if (chr.length > 1) 1905 return '' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; 1906 1907 return baseEntities[chr] || '' + chr.charCodeAt(0) + ';'; 1908 }); 1909 }, 1910 1911 encodeNamed : function(text, attr, entities) { 1912 entities = entities || namedEntities; 1913 1914 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1915 return baseEntities[chr] || entities[chr] || chr; 1916 }); 1917 }, 1918 1919 getEncodeFunc : function(name, entities) { 1920 var Entities = tinymce.html.Entities; 1921 1922 entities = buildEntitiesLookup(entities) || namedEntities; 1923 1924 function encodeNamedAndNumeric(text, attr) { 1925 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1926 return baseEntities[chr] || entities[chr] || '' + chr.charCodeAt(0) + ';' || chr; 1927 }); 1928 }; 1929 1930 function encodeCustomNamed(text, attr) { 1931 return Entities.encodeNamed(text, attr, entities); 1932 }; 1933 1934 // Replace + with , to be compatible with previous TinyMCE versions 1935 name = tinymce.makeMap(name.replace(/\+/g, ',')); 1936 1937 // Named and numeric encoder 1938 if (name.named && name.numeric) 1939 return encodeNamedAndNumeric; 1940 1941 // Named encoder 1942 if (name.named) { 1943 // Custom names 1944 if (entities) 1945 return encodeCustomNamed; 1946 1947 return Entities.encodeNamed; 1948 } 1949 1950 // Numeric 1951 if (name.numeric) 1952 return Entities.encodeNumeric; 1953 1954 // Raw encoder 1955 return Entities.encodeRaw; 1956 }, 1957 1958 decode : function(text) { 1959 return text.replace(entityRegExp, function(all, numeric, value) { 1960 if (numeric) { 1961 value = parseInt(value, numeric.length === 2 ? 16 : 10); 1962 1963 // Support upper UTF 1964 if (value > 0xFFFF) { 1965 value -= 0x10000; 1966 1967 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); 1968 } else 1969 return asciiMap[value] || String.fromCharCode(value); 1970 } 1971 1972 return reverseEntities[all] || namedEntities[all] || nativeDecode(all); 1973 }); 1974 } 1975 }; 1976 })(tinymce); 1977 1978 tinymce.html.Styles = function(settings, schema) { 1979 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, 1980 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, 1981 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, 1982 trimRightRegExp = /\s+$/, 1983 urlColorRegExp = /rgb/, 1984 undef, i, encodingLookup = {}, encodingItems; 1985 1986 settings = settings || {}; 1987 1988 encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' '); 1989 for (i = 0; i < encodingItems.length; i++) { 1990 encodingLookup[encodingItems[i]] = '\uFEFF' + i; 1991 encodingLookup['\uFEFF' + i] = encodingItems[i]; 1992 } 1993 1994 function toHex(match, r, g, b) { 1995 function hex(val) { 1996 val = parseInt(val).toString(16); 1997 1998 return val.length > 1 ? val : '0' + val; // 0 -> 00 1999 }; 2000 2001 return '#' + hex(r) + hex(g) + hex(b); 2002 }; 2003 2004 return { 2005 toHex : function(color) { 2006 return color.replace(rgbRegExp, toHex); 2007 }, 2008 2009 parse : function(css) { 2010 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this; 2011 2012 function compress(prefix, suffix) { 2013 var top, right, bottom, left; 2014 2015 // Get values and check it it needs compressing 2016 top = styles[prefix + '-top' + suffix]; 2017 if (!top) 2018 return; 2019 2020 right = styles[prefix + '-right' + suffix]; 2021 if (top != right) 2022 return; 2023 2024 bottom = styles[prefix + '-bottom' + suffix]; 2025 if (right != bottom) 2026 return; 2027 2028 left = styles[prefix + '-left' + suffix]; 2029 if (bottom != left) 2030 return; 2031 2032 // Compress 2033 styles[prefix + suffix] = left; 2034 delete styles[prefix + '-top' + suffix]; 2035 delete styles[prefix + '-right' + suffix]; 2036 delete styles[prefix + '-bottom' + suffix]; 2037 delete styles[prefix + '-left' + suffix]; 2038 }; 2039 2040 function canCompress(key) { 2041 var value = styles[key], i; 2042 2043 if (!value || value.indexOf(' ') < 0) 2044 return; 2045 2046 value = value.split(' '); 2047 i = value.length; 2048 while (i--) { 2049 if (value[i] !== value[0]) 2050 return false; 2051 } 2052 2053 styles[key] = value[0]; 2054 2055 return true; 2056 }; 2057 2058 function compress2(target, a, b, c) { 2059 if (!canCompress(a)) 2060 return; 2061 2062 if (!canCompress(b)) 2063 return; 2064 2065 if (!canCompress(c)) 2066 return; 2067 2068 // Compress 2069 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; 2070 delete styles[a]; 2071 delete styles[b]; 2072 delete styles[c]; 2073 }; 2074 2075 // Encodes the specified string by replacing all \" \' ; : with _<num> 2076 function encode(str) { 2077 isEncoded = true; 2078 2079 return encodingLookup[str]; 2080 }; 2081 2082 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc 2083 // It will also decode the \" \' if keep_slashes is set to fale or omitted 2084 function decode(str, keep_slashes) { 2085 if (isEncoded) { 2086 str = str.replace(/\uFEFF[0-9]/g, function(str) { 2087 return encodingLookup[str]; 2088 }); 2089 } 2090 2091 if (!keep_slashes) 2092 str = str.replace(/\\([\'\";:])/g, "$1"); 2093 2094 return str; 2095 }; 2096 2097 function processUrl(match, url, url2, url3, str, str2) { 2098 str = str || str2; 2099 2100 if (str) { 2101 str = decode(str); 2102 2103 // Force strings into single quote format 2104 return "'" + str.replace(/\'/g, "\\'") + "'"; 2105 } 2106 2107 url = decode(url || url2 || url3); 2108 2109 // Convert the URL to relative/absolute depending on config 2110 if (urlConverter) 2111 url = urlConverter.call(urlConverterScope, url, 'style'); 2112 2113 // Output new URL format 2114 return "url('" + url.replace(/\'/g, "\\'") + "')"; 2115 }; 2116 2117 if (css) { 2118 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing 2119 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { 2120 return str.replace(/[;:]/g, encode); 2121 }); 2122 2123 // Parse styles 2124 while (matches = styleRegExp.exec(css)) { 2125 name = matches[1].replace(trimRightRegExp, '').toLowerCase(); 2126 value = matches[2].replace(trimRightRegExp, ''); 2127 2128 if (name && value.length > 0) { 2129 // Opera will produce 700 instead of bold in their style values 2130 if (name === 'font-weight' && value === '700') 2131 value = 'bold'; 2132 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED 2133 value = value.toLowerCase(); 2134 2135 // Convert RGB colors to HEX 2136 value = value.replace(rgbRegExp, toHex); 2137 2138 // Convert URLs and force them into url('value') format 2139 value = value.replace(urlOrStrRegExp, processUrl); 2140 styles[name] = isEncoded ? decode(value, true) : value; 2141 } 2142 2143 styleRegExp.lastIndex = matches.index + matches[0].length; 2144 } 2145 2146 // Compress the styles to reduce it's size for example IE will expand styles 2147 compress("border", ""); 2148 compress("border", "-width"); 2149 compress("border", "-color"); 2150 compress("border", "-style"); 2151 compress("padding", ""); 2152 compress("margin", ""); 2153 compress2('border', 'border-width', 'border-style', 'border-color'); 2154 2155 // Remove pointless border, IE produces these 2156 if (styles.border === 'medium none') 2157 delete styles.border; 2158 } 2159 2160 return styles; 2161 }, 2162 2163 serialize : function(styles, element_name) { 2164 var css = '', name, value; 2165 2166 function serializeStyles(name) { 2167 var styleList, i, l, value; 2168 2169 styleList = schema.styles[name]; 2170 if (styleList) { 2171 for (i = 0, l = styleList.length; i < l; i++) { 2172 name = styleList[i]; 2173 value = styles[name]; 2174 2175 if (value !== undef && value.length > 0) 2176 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2177 } 2178 } 2179 }; 2180 2181 // Serialize styles according to schema 2182 if (element_name && schema && schema.styles) { 2183 // Serialize global styles and element specific styles 2184 serializeStyles('*'); 2185 serializeStyles(element_name); 2186 } else { 2187 // Output the styles in the order they are inside the object 2188 for (name in styles) { 2189 value = styles[name]; 2190 2191 if (value !== undef && value.length > 0) 2192 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2193 } 2194 } 2195 2196 return css; 2197 } 2198 }; 2199 }; 2200 2201 (function(tinymce) { 2202 var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each; 2203 2204 function split(str, delim) { 2205 return str.split(delim || ','); 2206 }; 2207 2208 function unpack(lookup, data) { 2209 var key, elements = {}; 2210 2211 function replace(value) { 2212 return value.replace(/[A-Z]+/g, function(key) { 2213 return replace(lookup[key]); 2214 }); 2215 }; 2216 2217 // Unpack lookup 2218 for (key in lookup) { 2219 if (lookup.hasOwnProperty(key)) 2220 lookup[key] = replace(lookup[key]); 2221 } 2222 2223 // Unpack and parse data into object map 2224 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) { 2225 attributes = split(attributes, '|'); 2226 2227 elements[name] = { 2228 attributes : makeMap(attributes), 2229 attributesOrder : attributes, 2230 children : makeMap(children, '|', {'#comment' : {}}) 2231 } 2232 }); 2233 2234 return elements; 2235 }; 2236 2237 function getHTML5() { 2238 var html5 = mapCache.html5; 2239 2240 if (!html5) { 2241 html5 = mapCache.html5 = unpack({ 2242 A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2243 B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' + 2244 'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr', 2245 C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' + 2246 'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' + 2247 'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video' 2248 }, 'html[A|manifest][body|head]' + 2249 'head[A][base|command|link|meta|noscript|script|style|title]' + 2250 'title[A][#]' + 2251 'base[A|href|target][]' + 2252 'link[A|href|rel|media|type|sizes][]' + 2253 'meta[A|http-equiv|name|content|charset][]' + 2254 'style[A|type|media|scoped][#]' + 2255 'script[A|charset|type|src|defer|async][#]' + 2256 'noscript[A][C]' + 2257 'body[A][C]' + 2258 'section[A][C]' + 2259 'nav[A][C]' + 2260 'article[A][C]' + 2261 'aside[A][C]' + 2262 'h1[A][B]' + 2263 'h2[A][B]' + 2264 'h3[A][B]' + 2265 'h4[A][B]' + 2266 'h5[A][B]' + 2267 'h6[A][B]' + 2268 'hgroup[A][h1|h2|h3|h4|h5|h6]' + 2269 'header[A][C]' + 2270 'footer[A][C]' + 2271 'address[A][C]' + 2272 'p[A][B]' + 2273 'br[A][]' + 2274 'pre[A][B]' + 2275 'dialog[A][dd|dt]' + 2276 'blockquote[A|cite][C]' + 2277 'ol[A|start|reversed][li]' + 2278 'ul[A][li]' + 2279 'li[A|value][C]' + 2280 'dl[A][dd|dt]' + 2281 'dt[A][B]' + 2282 'dd[A][C]' + 2283 'a[A|href|target|ping|rel|media|type][B]' + 2284 'em[A][B]' + 2285 'strong[A][B]' + 2286 'small[A][B]' + 2287 'cite[A][B]' + 2288 'q[A|cite][B]' + 2289 'dfn[A][B]' + 2290 'abbr[A][B]' + 2291 'code[A][B]' + 2292 'var[A][B]' + 2293 'samp[A][B]' + 2294 'kbd[A][B]' + 2295 'sub[A][B]' + 2296 'sup[A][B]' + 2297 'i[A][B]' + 2298 'b[A][B]' + 2299 'mark[A][B]' + 2300 'progress[A|value|max][B]' + 2301 'meter[A|value|min|max|low|high|optimum][B]' + 2302 'time[A|datetime][B]' + 2303 'ruby[A][B|rt|rp]' + 2304 'rt[A][B]' + 2305 'rp[A][B]' + 2306 'bdo[A][B]' + 2307 'span[A][B]' + 2308 'ins[A|cite|datetime][B]' + 2309 'del[A|cite|datetime][B]' + 2310 'figure[A][C|legend|figcaption]' + 2311 'figcaption[A][C]' + 2312 'img[A|alt|src|height|width|usemap|ismap][]' + 2313 'iframe[A|name|src|height|width|sandbox|seamless][]' + 2314 'embed[A|src|height|width|type][]' + 2315 'object[A|data|type|height|width|usemap|name|form|classid][param]' + 2316 'param[A|name|value][]' + 2317 'details[A|open][C|legend]' + 2318 'command[A|type|label|icon|disabled|checked|radiogroup][]' + 2319 'menu[A|type|label][C|li]' + 2320 'legend[A][C|B]' + 2321 'div[A][C]' + 2322 'source[A|src|type|media][]' + 2323 'audio[A|src|autobuffer|autoplay|loop|controls][source]' + 2324 'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' + 2325 'hr[A][]' + 2326 'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' + 2327 'fieldset[A|disabled|form|name][C|legend]' + 2328 'label[A|form|for][B]' + 2329 'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' + 2330 'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' + 2331 'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' + 2332 'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' + 2333 'datalist[A][B|option]' + 2334 'optgroup[A|disabled|label][option]' + 2335 'option[A|disabled|selected|label|value][]' + 2336 'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' + 2337 'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' + 2338 'output[A|for|form|name][B]' + 2339 'canvas[A|width|height][]' + 2340 'map[A|name][B|C]' + 2341 'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' + 2342 'mathml[A][]' + 2343 'svg[A][]' + 2344 'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' + 2345 'caption[A][C]' + 2346 'colgroup[A|span][col]' + 2347 'col[A|span][]' + 2348 'thead[A][tr]' + 2349 'tfoot[A][tr]' + 2350 'tbody[A][tr]' + 2351 'tr[A][th|td]' + 2352 'th[A|headers|rowspan|colspan|scope][B]' + 2353 'td[A|headers|rowspan|colspan][C]' + 2354 'wbr[A][]' 2355 ); 2356 } 2357 2358 return html5; 2359 }; 2360 2361 function getHTML4() { 2362 var html4 = mapCache.html4; 2363 2364 if (!html4) { 2365 // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size 2366 html4 = mapCache.html4 = unpack({ 2367 Z : 'H|K|N|O|P', 2368 Y : 'X|form|R|Q', 2369 ZG : 'E|span|width|align|char|charoff|valign', 2370 X : 'p|T|div|U|W|isindex|fieldset|table', 2371 ZF : 'E|align|char|charoff|valign', 2372 W : 'pre|hr|blockquote|address|center|noframes', 2373 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height', 2374 ZD : '[E][S]', 2375 U : 'ul|ol|dl|menu|dir', 2376 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q', 2377 T : 'h1|h2|h3|h4|h5|h6', 2378 ZB : 'X|S|Q', 2379 S : 'R|P', 2380 ZA : 'a|G|J|M|O|P', 2381 R : 'a|H|K|N|O', 2382 Q : 'noscript|P', 2383 P : 'ins|del|script', 2384 O : 'input|select|textarea|label|button', 2385 N : 'M|L', 2386 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym', 2387 L : 'sub|sup', 2388 K : 'J|I', 2389 J : 'tt|i|b|u|s|strike', 2390 I : 'big|small|font|basefont', 2391 H : 'G|F', 2392 G : 'br|span|bdo', 2393 F : 'object|applet|img|map|iframe', 2394 E : 'A|B|C', 2395 D : 'accesskey|tabindex|onfocus|onblur', 2396 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2397 B : 'lang|xml:lang|dir', 2398 A : 'id|class|style|title' 2399 }, 'script[id|charset|type|language|src|defer|xml:space][]' + 2400 'style[B|id|type|media|title|xml:space][]' + 2401 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 2402 'param[id|name|value|valuetype|type][]' + 2403 'p[E|align][#|S]' + 2404 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 2405 'br[A|clear][]' + 2406 'span[E][#|S]' + 2407 'bdo[A|C|B][#|S]' + 2408 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 2409 'h1[E|align][#|S]' + 2410 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 2411 'map[B|C|A|name][X|form|Q|area]' + 2412 'h2[E|align][#|S]' + 2413 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 2414 'h3[E|align][#|S]' + 2415 'tt[E][#|S]' + 2416 'i[E][#|S]' + 2417 'b[E][#|S]' + 2418 'u[E][#|S]' + 2419 's[E][#|S]' + 2420 'strike[E][#|S]' + 2421 'big[E][#|S]' + 2422 'small[E][#|S]' + 2423 'font[A|B|size|color|face][#|S]' + 2424 'basefont[id|size|color|face][]' + 2425 'em[E][#|S]' + 2426 'strong[E][#|S]' + 2427 'dfn[E][#|S]' + 2428 'code[E][#|S]' + 2429 'q[E|cite][#|S]' + 2430 'samp[E][#|S]' + 2431 'kbd[E][#|S]' + 2432 'var[E][#|S]' + 2433 'cite[E][#|S]' + 2434 'abbr[E][#|S]' + 2435 'acronym[E][#|S]' + 2436 'sub[E][#|S]' + 2437 'sup[E][#|S]' + 2438 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 2439 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 2440 'optgroup[E|disabled|label][option]' + 2441 'option[E|selected|disabled|label|value][]' + 2442 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 2443 'label[E|for|accesskey|onfocus|onblur][#|S]' + 2444 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 2445 'h4[E|align][#|S]' + 2446 'ins[E|cite|datetime][#|Y]' + 2447 'h5[E|align][#|S]' + 2448 'del[E|cite|datetime][#|Y]' + 2449 'h6[E|align][#|S]' + 2450 'div[E|align][#|Y]' + 2451 'ul[E|type|compact][li]' + 2452 'li[E|type|value][#|Y]' + 2453 'ol[E|type|compact|start][li]' + 2454 'dl[E|compact][dt|dd]' + 2455 'dt[E][#|S]' + 2456 'dd[E][#|Y]' + 2457 'menu[E|compact][li]' + 2458 'dir[E|compact][li]' + 2459 'pre[E|width|xml:space][#|ZA]' + 2460 'hr[E|align|noshade|size|width][]' + 2461 'blockquote[E|cite][#|Y]' + 2462 'address[E][#|S|p]' + 2463 'center[E][#|Y]' + 2464 'noframes[E][#|Y]' + 2465 'isindex[A|B|prompt][]' + 2466 'fieldset[E][#|legend|Y]' + 2467 'legend[E|accesskey|align][#|S]' + 2468 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 2469 'caption[E|align][#|S]' + 2470 'col[ZG][]' + 2471 'colgroup[ZG][col]' + 2472 'thead[ZF][tr]' + 2473 'tr[ZF|bgcolor][th|td]' + 2474 'th[E|ZE][#|Y]' + 2475 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 2476 'noscript[E][#|Y]' + 2477 'td[E|ZE][#|Y]' + 2478 'tfoot[ZF][tr]' + 2479 'tbody[ZF][tr]' + 2480 'area[E|D|shape|coords|href|nohref|alt|target][]' + 2481 'base[id|href|target][]' + 2482 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]' 2483 ); 2484 } 2485 2486 return html4; 2487 }; 2488 2489 tinymce.html.Schema = function(settings) { 2490 var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems; 2491 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {}; 2492 2493 // Creates an lookup table map object for the specified option or the default value 2494 function createLookupTable(option, default_value, extend) { 2495 var value = settings[option]; 2496 2497 if (!value) { 2498 // Get cached default map or make it if needed 2499 value = mapCache[option]; 2500 2501 if (!value) { 2502 value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' ')); 2503 value = tinymce.extend(value, extend); 2504 2505 mapCache[option] = value; 2506 } 2507 } else { 2508 // Create custom map 2509 value = makeMap(value, ',', makeMap(value.toUpperCase(), ' ')); 2510 } 2511 2512 return value; 2513 }; 2514 2515 settings = settings || {}; 2516 schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4(); 2517 2518 // Allow all elements and attributes if verify_html is set to false 2519 if (settings.verify_html === false) 2520 settings.valid_elements = '*[*]'; 2521 2522 // Build styles list 2523 if (settings.valid_styles) { 2524 validStyles = {}; 2525 2526 // Convert styles into a rule list 2527 each(settings.valid_styles, function(value, key) { 2528 validStyles[key] = tinymce.explode(value); 2529 }); 2530 } 2531 2532 // Setup map objects 2533 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea'); 2534 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr'); 2535 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr'); 2536 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls'); 2537 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap); 2538 blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + 2539 'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + 2540 'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup'); 2541 2542 // Converts a wildcard expression string to a regexp for example *a will become /.*a/. 2543 function patternToRegExp(str) { 2544 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); 2545 }; 2546 2547 // Parses the specified valid_elements string and adds to the current rules 2548 // This function is a bit hard to read since it's heavily optimized for speed 2549 function addValidElements(valid_elements) { 2550 var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, 2551 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value, 2552 elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/, 2553 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, 2554 hasPatternsRegExp = /[*?+]/; 2555 2556 if (valid_elements) { 2557 // Split valid elements into an array with rules 2558 valid_elements = split(valid_elements); 2559 2560 if (elements['@']) { 2561 globalAttributes = elements['@'].attributes; 2562 globalAttributesOrder = elements['@'].attributesOrder; 2563 } 2564 2565 // Loop all rules 2566 for (ei = 0, el = valid_elements.length; ei < el; ei++) { 2567 // Parse element rule 2568 matches = elementRuleRegExp.exec(valid_elements[ei]); 2569 if (matches) { 2570 // Setup local names for matches 2571 prefix = matches[1]; 2572 elementName = matches[2]; 2573 outputName = matches[3]; 2574 attrData = matches[4]; 2575 2576 // Create new attributes and attributesOrder 2577 attributes = {}; 2578 attributesOrder = []; 2579 2580 // Create the new element 2581 element = { 2582 attributes : attributes, 2583 attributesOrder : attributesOrder 2584 }; 2585 2586 // Padd empty elements prefix 2587 if (prefix === '#') 2588 element.paddEmpty = true; 2589 2590 // Remove empty elements prefix 2591 if (prefix === '-') 2592 element.removeEmpty = true; 2593 2594 // Copy attributes from global rule into current rule 2595 if (globalAttributes) { 2596 for (key in globalAttributes) 2597 attributes[key] = globalAttributes[key]; 2598 2599 attributesOrder.push.apply(attributesOrder, globalAttributesOrder); 2600 } 2601 2602 // Attributes defined 2603 if (attrData) { 2604 attrData = split(attrData, '|'); 2605 for (ai = 0, al = attrData.length; ai < al; ai++) { 2606 matches = attrRuleRegExp.exec(attrData[ai]); 2607 if (matches) { 2608 attr = {}; 2609 attrType = matches[1]; 2610 attrName = matches[2].replace(/::/g, ':'); 2611 prefix = matches[3]; 2612 value = matches[4]; 2613 2614 // Required 2615 if (attrType === '!') { 2616 element.attributesRequired = element.attributesRequired || []; 2617 element.attributesRequired.push(attrName); 2618 attr.required = true; 2619 } 2620 2621 // Denied from global 2622 if (attrType === '-') { 2623 delete attributes[attrName]; 2624 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1); 2625 continue; 2626 } 2627 2628 // Default value 2629 if (prefix) { 2630 // Default value 2631 if (prefix === '=') { 2632 element.attributesDefault = element.attributesDefault || []; 2633 element.attributesDefault.push({name: attrName, value: value}); 2634 attr.defaultValue = value; 2635 } 2636 2637 // Forced value 2638 if (prefix === ':') { 2639 element.attributesForced = element.attributesForced || []; 2640 element.attributesForced.push({name: attrName, value: value}); 2641 attr.forcedValue = value; 2642 } 2643 2644 // Required values 2645 if (prefix === '<') 2646 attr.validValues = makeMap(value, '?'); 2647 } 2648 2649 // Check for attribute patterns 2650 if (hasPatternsRegExp.test(attrName)) { 2651 element.attributePatterns = element.attributePatterns || []; 2652 attr.pattern = patternToRegExp(attrName); 2653 element.attributePatterns.push(attr); 2654 } else { 2655 // Add attribute to order list if it doesn't already exist 2656 if (!attributes[attrName]) 2657 attributesOrder.push(attrName); 2658 2659 attributes[attrName] = attr; 2660 } 2661 } 2662 } 2663 } 2664 2665 // Global rule, store away these for later usage 2666 if (!globalAttributes && elementName == '@') { 2667 globalAttributes = attributes; 2668 globalAttributesOrder = attributesOrder; 2669 } 2670 2671 // Handle substitute elements such as b/strong 2672 if (outputName) { 2673 element.outputName = elementName; 2674 elements[outputName] = element; 2675 } 2676 2677 // Add pattern or exact element 2678 if (hasPatternsRegExp.test(elementName)) { 2679 element.pattern = patternToRegExp(elementName); 2680 patternElements.push(element); 2681 } else 2682 elements[elementName] = element; 2683 } 2684 } 2685 } 2686 }; 2687 2688 function setValidElements(valid_elements) { 2689 elements = {}; 2690 patternElements = []; 2691 2692 addValidElements(valid_elements); 2693 2694 each(schemaItems, function(element, name) { 2695 children[name] = element.children; 2696 }); 2697 }; 2698 2699 // Adds custom non HTML elements to the schema 2700 function addCustomElements(custom_elements) { 2701 var customElementRegExp = /^(~)?(.+)$/; 2702 2703 if (custom_elements) { 2704 each(split(custom_elements), function(rule) { 2705 var matches = customElementRegExp.exec(rule), 2706 inline = matches[1] === '~', 2707 cloneName = inline ? 'span' : 'div', 2708 name = matches[2]; 2709 2710 children[name] = children[cloneName]; 2711 customElementsMap[name] = cloneName; 2712 2713 // If it's not marked as inline then add it to valid block elements 2714 if (!inline) 2715 blockElementsMap[name] = {}; 2716 2717 // Add custom elements at span/div positions 2718 each(children, function(element, child) { 2719 if (element[cloneName]) 2720 element[name] = element[cloneName]; 2721 }); 2722 }); 2723 } 2724 }; 2725 2726 // Adds valid children to the schema object 2727 function addValidChildren(valid_children) { 2728 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; 2729 2730 if (valid_children) { 2731 each(split(valid_children), function(rule) { 2732 var matches = childRuleRegExp.exec(rule), parent, prefix; 2733 2734 if (matches) { 2735 prefix = matches[1]; 2736 2737 // Add/remove items from default 2738 if (prefix) 2739 parent = children[matches[2]]; 2740 else 2741 parent = children[matches[2]] = {'#comment' : {}}; 2742 2743 parent = children[matches[2]]; 2744 2745 each(split(matches[3], '|'), function(child) { 2746 if (prefix === '-') 2747 delete parent[child]; 2748 else 2749 parent[child] = {}; 2750 }); 2751 } 2752 }); 2753 } 2754 }; 2755 2756 function getElementRule(name) { 2757 var element = elements[name], i; 2758 2759 // Exact match found 2760 if (element) 2761 return element; 2762 2763 // No exact match then try the patterns 2764 i = patternElements.length; 2765 while (i--) { 2766 element = patternElements[i]; 2767 2768 if (element.pattern.test(name)) 2769 return element; 2770 } 2771 }; 2772 2773 if (!settings.valid_elements) { 2774 // No valid elements defined then clone the elements from the schema spec 2775 each(schemaItems, function(element, name) { 2776 elements[name] = { 2777 attributes : element.attributes, 2778 attributesOrder : element.attributesOrder 2779 }; 2780 2781 children[name] = element.children; 2782 }); 2783 2784 // Switch these on HTML4 2785 if (settings.schema != "html5") { 2786 each(split('strong/b,em/i'), function(item) { 2787 item = split(item, '/'); 2788 elements[item[1]].outputName = item[0]; 2789 }); 2790 } 2791 2792 // Add default alt attribute for images 2793 elements.img.attributesDefault = [{name: 'alt', value: ''}]; 2794 2795 // Remove these if they are empty by default 2796 each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) { 2797 if (elements[name]) { 2798 elements[name].removeEmpty = true; 2799 } 2800 }); 2801 2802 // Padd these by default 2803 each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) { 2804 elements[name].paddEmpty = true; 2805 }); 2806 } else 2807 setValidElements(settings.valid_elements); 2808 2809 addCustomElements(settings.custom_elements); 2810 addValidChildren(settings.valid_children); 2811 addValidElements(settings.extended_valid_elements); 2812 2813 // Todo: Remove this when we fix list handling to be valid 2814 addValidChildren('+ol[ul|ol],+ul[ul|ol]'); 2815 2816 // Delete invalid elements 2817 if (settings.invalid_elements) { 2818 tinymce.each(tinymce.explode(settings.invalid_elements), function(item) { 2819 if (elements[item]) 2820 delete elements[item]; 2821 }); 2822 } 2823 2824 // If the user didn't allow span only allow internal spans 2825 if (!getElementRule('span')) 2826 addValidElements('span[!data-mce-type|*]'); 2827 2828 self.children = children; 2829 2830 self.styles = validStyles; 2831 2832 self.getBoolAttrs = function() { 2833 return boolAttrMap; 2834 }; 2835 2836 self.getBlockElements = function() { 2837 return blockElementsMap; 2838 }; 2839 2840 self.getShortEndedElements = function() { 2841 return shortEndedElementsMap; 2842 }; 2843 2844 self.getSelfClosingElements = function() { 2845 return selfClosingElementsMap; 2846 }; 2847 2848 self.getNonEmptyElements = function() { 2849 return nonEmptyElementsMap; 2850 }; 2851 2852 self.getWhiteSpaceElements = function() { 2853 return whiteSpaceElementsMap; 2854 }; 2855 2856 self.isValidChild = function(name, child) { 2857 var parent = children[name]; 2858 2859 return !!(parent && parent[child]); 2860 }; 2861 2862 self.isValid = function(name, attr) { 2863 var attrPatterns, i, rule = getElementRule(name); 2864 2865 // Check if it's a valid element 2866 if (rule) { 2867 if (attr) { 2868 // Check if attribute name exists 2869 if (rule.attributes[attr]) { 2870 return true; 2871 } 2872 2873 // Check if attribute matches a regexp pattern 2874 attrPatterns = rule.attributePatterns; 2875 if (attrPatterns) { 2876 i = attrPatterns.length; 2877 while (i--) { 2878 if (attrPatterns[i].pattern.test(name)) { 2879 return true; 2880 } 2881 } 2882 } 2883 } else { 2884 return true; 2885 } 2886 } 2887 2888 // No match 2889 return false; 2890 }; 2891 2892 self.getElementRule = getElementRule; 2893 2894 self.getCustomElements = function() { 2895 return customElementsMap; 2896 }; 2897 2898 self.addValidElements = addValidElements; 2899 2900 self.setValidElements = setValidElements; 2901 2902 self.addCustomElements = addCustomElements; 2903 2904 self.addValidChildren = addValidChildren; 2905 }; 2906 })(tinymce); 2907 2908 (function(tinymce) { 2909 tinymce.html.SaxParser = function(settings, schema) { 2910 var self = this, noop = function() {}; 2911 2912 settings = settings || {}; 2913 self.schema = schema = schema || new tinymce.html.Schema(); 2914 2915 if (settings.fix_self_closing !== false) 2916 settings.fix_self_closing = true; 2917 2918 // Add handler functions from settings and setup default handlers 2919 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) { 2920 if (name) 2921 self[name] = settings[name] || noop; 2922 }); 2923 2924 self.parse = function(html) { 2925 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements, 2926 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp, 2927 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing, 2928 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE; 2929 2930 function processEndTag(name) { 2931 var pos, i; 2932 2933 // Find position of parent of the same type 2934 pos = stack.length; 2935 while (pos--) { 2936 if (stack[pos].name === name) 2937 break; 2938 } 2939 2940 // Found parent 2941 if (pos >= 0) { 2942 // Close all the open elements 2943 for (i = stack.length - 1; i >= pos; i--) { 2944 name = stack[i]; 2945 2946 if (name.valid) 2947 self.end(name.name); 2948 } 2949 2950 // Remove the open elements from the stack 2951 stack.length = pos; 2952 } 2953 }; 2954 2955 function parseAttribute(match, name, value, val2, val3) { 2956 var attrRule, i; 2957 2958 name = name.toLowerCase(); 2959 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute 2960 2961 // Validate name and value 2962 if (validate && !isInternalElement && name.indexOf('data-') !== 0) { 2963 attrRule = validAttributesMap[name]; 2964 2965 // Find rule by pattern matching 2966 if (!attrRule && validAttributePatterns) { 2967 i = validAttributePatterns.length; 2968 while (i--) { 2969 attrRule = validAttributePatterns[i]; 2970 if (attrRule.pattern.test(name)) 2971 break; 2972 } 2973 2974 // No rule matched 2975 if (i === -1) 2976 attrRule = null; 2977 } 2978 2979 // No attribute rule found 2980 if (!attrRule) 2981 return; 2982 2983 // Validate value 2984 if (attrRule.validValues && !(value in attrRule.validValues)) 2985 return; 2986 } 2987 2988 // Add attribute to list and map 2989 attrList.map[name] = value; 2990 attrList.push({ 2991 name: name, 2992 value: value 2993 }); 2994 }; 2995 2996 // Precompile RegExps and map objects 2997 tokenRegExp = new RegExp('<(?:' + 2998 '(?:!--([\\w\\W]*?)-->)|' + // Comment 2999 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA 3000 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE 3001 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI 3002 '(?:\\/([^>]+)>)|' + // End element 3003 '(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element 3004 ')', 'g'); 3005 3006 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g; 3007 specialElements = { 3008 'script' : /<\/script[^>]*>/gi, 3009 'style' : /<\/style[^>]*>/gi, 3010 'noscript' : /<\/noscript[^>]*>/gi 3011 }; 3012 3013 // Setup lookup tables for empty elements and boolean attributes 3014 shortEndedElements = schema.getShortEndedElements(); 3015 selfClosing = settings.self_closing_elements || schema.getSelfClosingElements(); 3016 fillAttrsMap = schema.getBoolAttrs(); 3017 validate = settings.validate; 3018 removeInternalElements = settings.remove_internals; 3019 fixSelfClosing = settings.fix_self_closing; 3020 isIE = tinymce.isIE; 3021 invalidPrefixRegExp = /^:/; 3022 3023 while (matches = tokenRegExp.exec(html)) { 3024 // Text 3025 if (index < matches.index) 3026 self.text(decode(html.substr(index, matches.index - index))); 3027 3028 if (value = matches[6]) { // End element 3029 value = value.toLowerCase(); 3030 3031 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 3032 if (isIE && invalidPrefixRegExp.test(value)) 3033 value = value.substr(1); 3034 3035 processEndTag(value); 3036 } else if (value = matches[7]) { // Start element 3037 value = value.toLowerCase(); 3038 3039 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 3040 if (isIE && invalidPrefixRegExp.test(value)) 3041 value = value.substr(1); 3042 3043 isShortEnded = value in shortEndedElements; 3044 3045 // Is self closing tag for example an <li> after an open <li> 3046 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) 3047 processEndTag(value); 3048 3049 // Validate element 3050 if (!validate || (elementRule = schema.getElementRule(value))) { 3051 isValidElement = true; 3052 3053 // Grab attributes map and patters when validation is enabled 3054 if (validate) { 3055 validAttributesMap = elementRule.attributes; 3056 validAttributePatterns = elementRule.attributePatterns; 3057 } 3058 3059 // Parse attributes 3060 if (attribsValue = matches[8]) { 3061 isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element 3062 3063 // If the element has internal attributes then remove it if we are told to do so 3064 if (isInternalElement && removeInternalElements) 3065 isValidElement = false; 3066 3067 attrList = []; 3068 attrList.map = {}; 3069 3070 attribsValue.replace(attrRegExp, parseAttribute); 3071 } else { 3072 attrList = []; 3073 attrList.map = {}; 3074 } 3075 3076 // Process attributes if validation is enabled 3077 if (validate && !isInternalElement) { 3078 attributesRequired = elementRule.attributesRequired; 3079 attributesDefault = elementRule.attributesDefault; 3080 attributesForced = elementRule.attributesForced; 3081 3082 // Handle forced attributes 3083 if (attributesForced) { 3084 i = attributesForced.length; 3085 while (i--) { 3086 attr = attributesForced[i]; 3087 name = attr.name; 3088 attrValue = attr.value; 3089 3090 if (attrValue === '{$uid}') 3091 attrValue = 'mce_' + idCount++; 3092 3093 attrList.map[name] = attrValue; 3094 attrList.push({name: name, value: attrValue}); 3095 } 3096 } 3097 3098 // Handle default attributes 3099 if (attributesDefault) { 3100 i = attributesDefault.length; 3101 while (i--) { 3102 attr = attributesDefault[i]; 3103 name = attr.name; 3104 3105 if (!(name in attrList.map)) { 3106 attrValue = attr.value; 3107 3108 if (attrValue === '{$uid}') 3109 attrValue = 'mce_' + idCount++; 3110 3111 attrList.map[name] = attrValue; 3112 attrList.push({name: name, value: attrValue}); 3113 } 3114 } 3115 } 3116 3117 // Handle required attributes 3118 if (attributesRequired) { 3119 i = attributesRequired.length; 3120 while (i--) { 3121 if (attributesRequired[i] in attrList.map) 3122 break; 3123 } 3124 3125 // None of the required attributes where found 3126 if (i === -1) 3127 isValidElement = false; 3128 } 3129 3130 // Invalidate element if it's marked as bogus 3131 if (attrList.map['data-mce-bogus']) 3132 isValidElement = false; 3133 } 3134 3135 if (isValidElement) 3136 self.start(value, attrList, isShortEnded); 3137 } else 3138 isValidElement = false; 3139 3140 // Treat script, noscript and style a bit different since they may include code that looks like elements 3141 if (endRegExp = specialElements[value]) { 3142 endRegExp.lastIndex = index = matches.index + matches[0].length; 3143 3144 if (matches = endRegExp.exec(html)) { 3145 if (isValidElement) 3146 text = html.substr(index, matches.index - index); 3147 3148 index = matches.index + matches[0].length; 3149 } else { 3150 text = html.substr(index); 3151 index = html.length; 3152 } 3153 3154 if (isValidElement && text.length > 0) 3155 self.text(text, true); 3156 3157 if (isValidElement) 3158 self.end(value); 3159 3160 tokenRegExp.lastIndex = index; 3161 continue; 3162 } 3163 3164 // Push value on to stack 3165 if (!isShortEnded) { 3166 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) 3167 stack.push({name: value, valid: isValidElement}); 3168 else if (isValidElement) 3169 self.end(value); 3170 } 3171 } else if (value = matches[1]) { // Comment 3172 self.comment(value); 3173 } else if (value = matches[2]) { // CDATA 3174 self.cdata(value); 3175 } else if (value = matches[3]) { // DOCTYPE 3176 self.doctype(value); 3177 } else if (value = matches[4]) { // PI 3178 self.pi(value, matches[5]); 3179 } 3180 3181 index = matches.index + matches[0].length; 3182 } 3183 3184 // Text 3185 if (index < html.length) 3186 self.text(decode(html.substr(index))); 3187 3188 // Close any open elements 3189 for (i = stack.length - 1; i >= 0; i--) { 3190 value = stack[i]; 3191 3192 if (value.valid) 3193 self.end(value.name); 3194 } 3195 }; 3196 } 3197 })(tinymce); 3198 3199 (function(tinymce) { 3200 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { 3201 '#text' : 3, 3202 '#comment' : 8, 3203 '#cdata' : 4, 3204 '#pi' : 7, 3205 '#doctype' : 10, 3206 '#document-fragment' : 11 3207 }; 3208 3209 // Walks the tree left/right 3210 function walk(node, root_node, prev) { 3211 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; 3212 3213 // Walk into nodes if it has a start 3214 if (node[startName]) 3215 return node[startName]; 3216 3217 // Return the sibling if it has one 3218 if (node !== root_node) { 3219 sibling = node[siblingName]; 3220 3221 if (sibling) 3222 return sibling; 3223 3224 // Walk up the parents to look for siblings 3225 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { 3226 sibling = parent[siblingName]; 3227 3228 if (sibling) 3229 return sibling; 3230 } 3231 } 3232 }; 3233 3234 function Node(name, type) { 3235 this.name = name; 3236 this.type = type; 3237 3238 if (type === 1) { 3239 this.attributes = []; 3240 this.attributes.map = {}; 3241 } 3242 } 3243 3244 tinymce.extend(Node.prototype, { 3245 replace : function(node) { 3246 var self = this; 3247 3248 if (node.parent) 3249 node.remove(); 3250 3251 self.insert(node, self); 3252 self.remove(); 3253 3254 return self; 3255 }, 3256 3257 attr : function(name, value) { 3258 var self = this, attrs, i, undef; 3259 3260 if (typeof name !== "string") { 3261 for (i in name) 3262 self.attr(i, name[i]); 3263 3264 return self; 3265 } 3266 3267 if (attrs = self.attributes) { 3268 if (value !== undef) { 3269 // Remove attribute 3270 if (value === null) { 3271 if (name in attrs.map) { 3272 delete attrs.map[name]; 3273 3274 i = attrs.length; 3275 while (i--) { 3276 if (attrs[i].name === name) { 3277 attrs = attrs.splice(i, 1); 3278 return self; 3279 } 3280 } 3281 } 3282 3283 return self; 3284 } 3285 3286 // Set attribute 3287 if (name in attrs.map) { 3288 // Set attribute 3289 i = attrs.length; 3290 while (i--) { 3291 if (attrs[i].name === name) { 3292 attrs[i].value = value; 3293 break; 3294 } 3295 } 3296 } else 3297 attrs.push({name: name, value: value}); 3298 3299 attrs.map[name] = value; 3300 3301 return self; 3302 } else { 3303 return attrs.map[name]; 3304 } 3305 } 3306 }, 3307 3308 clone : function() { 3309 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; 3310 3311 // Clone element attributes 3312 if (selfAttrs = self.attributes) { 3313 cloneAttrs = []; 3314 cloneAttrs.map = {}; 3315 3316 for (i = 0, l = selfAttrs.length; i < l; i++) { 3317 selfAttr = selfAttrs[i]; 3318 3319 // Clone everything except id 3320 if (selfAttr.name !== 'id') { 3321 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; 3322 cloneAttrs.map[selfAttr.name] = selfAttr.value; 3323 } 3324 } 3325 3326 clone.attributes = cloneAttrs; 3327 } 3328 3329 clone.value = self.value; 3330 clone.shortEnded = self.shortEnded; 3331 3332 return clone; 3333 }, 3334 3335 wrap : function(wrapper) { 3336 var self = this; 3337 3338 self.parent.insert(wrapper, self); 3339 wrapper.append(self); 3340 3341 return self; 3342 }, 3343 3344 unwrap : function() { 3345 var self = this, node, next; 3346 3347 for (node = self.firstChild; node; ) { 3348 next = node.next; 3349 self.insert(node, self, true); 3350 node = next; 3351 } 3352 3353 self.remove(); 3354 }, 3355 3356 remove : function() { 3357 var self = this, parent = self.parent, next = self.next, prev = self.prev; 3358 3359 if (parent) { 3360 if (parent.firstChild === self) { 3361 parent.firstChild = next; 3362 3363 if (next) 3364 next.prev = null; 3365 } else { 3366 prev.next = next; 3367 } 3368 3369 if (parent.lastChild === self) { 3370 parent.lastChild = prev; 3371 3372 if (prev) 3373 prev.next = null; 3374 } else { 3375 next.prev = prev; 3376 } 3377 3378 self.parent = self.next = self.prev = null; 3379 } 3380 3381 return self; 3382 }, 3383 3384 append : function(node) { 3385 var self = this, last; 3386 3387 if (node.parent) 3388 node.remove(); 3389 3390 last = self.lastChild; 3391 if (last) { 3392 last.next = node; 3393 node.prev = last; 3394 self.lastChild = node; 3395 } else 3396 self.lastChild = self.firstChild = node; 3397 3398 node.parent = self; 3399 3400 return node; 3401 }, 3402 3403 insert : function(node, ref_node, before) { 3404 var parent; 3405 3406 if (node.parent) 3407 node.remove(); 3408 3409 parent = ref_node.parent || this; 3410 3411 if (before) { 3412 if (ref_node === parent.firstChild) 3413 parent.firstChild = node; 3414 else 3415 ref_node.prev.next = node; 3416 3417 node.prev = ref_node.prev; 3418 node.next = ref_node; 3419 ref_node.prev = node; 3420 } else { 3421 if (ref_node === parent.lastChild) 3422 parent.lastChild = node; 3423 else 3424 ref_node.next.prev = node; 3425 3426 node.next = ref_node.next; 3427 node.prev = ref_node; 3428 ref_node.next = node; 3429 } 3430 3431 node.parent = parent; 3432 3433 return node; 3434 }, 3435 3436 getAll : function(name) { 3437 var self = this, node, collection = []; 3438 3439 for (node = self.firstChild; node; node = walk(node, self)) { 3440 if (node.name === name) 3441 collection.push(node); 3442 } 3443 3444 return collection; 3445 }, 3446 3447 empty : function() { 3448 var self = this, nodes, i, node; 3449 3450 // Remove all children 3451 if (self.firstChild) { 3452 nodes = []; 3453 3454 // Collect the children 3455 for (node = self.firstChild; node; node = walk(node, self)) 3456 nodes.push(node); 3457 3458 // Remove the children 3459 i = nodes.length; 3460 while (i--) { 3461 node = nodes[i]; 3462 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; 3463 } 3464 } 3465 3466 self.firstChild = self.lastChild = null; 3467 3468 return self; 3469 }, 3470 3471 isEmpty : function(elements) { 3472 var self = this, node = self.firstChild, i, name; 3473 3474 if (node) { 3475 do { 3476 if (node.type === 1) { 3477 // Ignore bogus elements 3478 if (node.attributes.map['data-mce-bogus']) 3479 continue; 3480 3481 // Keep empty elements like <img /> 3482 if (elements[node.name]) 3483 return false; 3484 3485 // Keep elements with data attributes or name attribute like <a name="1"></a> 3486 i = node.attributes.length; 3487 while (i--) { 3488 name = node.attributes[i].name; 3489 if (name === "name" || name.indexOf('data-') === 0) 3490 return false; 3491 } 3492 } 3493 3494 // Keep comments 3495 if (node.type === 8) 3496 return false; 3497 3498 // Keep non whitespace text nodes 3499 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) 3500 return false; 3501 } while (node = walk(node, self)); 3502 } 3503 3504 return true; 3505 }, 3506 3507 walk : function(prev) { 3508 return walk(this, null, prev); 3509 } 3510 }); 3511 3512 tinymce.extend(Node, { 3513 create : function(name, attrs) { 3514 var node, attrName; 3515 3516 // Create node 3517 node = new Node(name, typeLookup[name] || 1); 3518 3519 // Add attributes if needed 3520 if (attrs) { 3521 for (attrName in attrs) 3522 node.attr(attrName, attrs[attrName]); 3523 } 3524 3525 return node; 3526 } 3527 }); 3528 3529 tinymce.html.Node = Node; 3530 })(tinymce); 3531 3532 (function(tinymce) { 3533 var Node = tinymce.html.Node; 3534 3535 tinymce.html.DomParser = function(settings, schema) { 3536 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; 3537 3538 settings = settings || {}; 3539 settings.validate = "validate" in settings ? settings.validate : true; 3540 settings.root_name = settings.root_name || 'body'; 3541 self.schema = schema = schema || new tinymce.html.Schema(); 3542 3543 function fixInvalidChildren(nodes) { 3544 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, 3545 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode; 3546 3547 nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); 3548 nonEmptyElements = schema.getNonEmptyElements(); 3549 3550 for (ni = 0; ni < nodes.length; ni++) { 3551 node = nodes[ni]; 3552 3553 // Already removed 3554 if (!node.parent) 3555 continue; 3556 3557 // Get list of all parent nodes until we find a valid parent to stick the child into 3558 parents = [node]; 3559 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) 3560 parents.push(parent); 3561 3562 // Found a suitable parent 3563 if (parent && parents.length > 1) { 3564 // Reverse the array since it makes looping easier 3565 parents.reverse(); 3566 3567 // Clone the related parent and insert that after the moved node 3568 newParent = currentNode = self.filterNode(parents[0].clone()); 3569 3570 // Start cloning and moving children on the left side of the target node 3571 for (i = 0; i < parents.length - 1; i++) { 3572 if (schema.isValidChild(currentNode.name, parents[i].name)) { 3573 tempNode = self.filterNode(parents[i].clone()); 3574 currentNode.append(tempNode); 3575 } else 3576 tempNode = currentNode; 3577 3578 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { 3579 nextNode = childNode.next; 3580 tempNode.append(childNode); 3581 childNode = nextNode; 3582 } 3583 3584 currentNode = tempNode; 3585 } 3586 3587 if (!newParent.isEmpty(nonEmptyElements)) { 3588 parent.insert(newParent, parents[0], true); 3589 parent.insert(node, newParent); 3590 } else { 3591 parent.insert(node, parents[0], true); 3592 } 3593 3594 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p> 3595 parent = parents[0]; 3596 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { 3597 parent.empty().remove(); 3598 } 3599 } else if (node.parent) { 3600 // If it's an LI try to find a UL/OL for it or wrap it 3601 if (node.name === 'li') { 3602 sibling = node.prev; 3603 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3604 sibling.append(node); 3605 continue; 3606 } 3607 3608 sibling = node.next; 3609 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3610 sibling.insert(node, sibling.firstChild, true); 3611 continue; 3612 } 3613 3614 node.wrap(self.filterNode(new Node('ul', 1))); 3615 continue; 3616 } 3617 3618 // Try wrapping the element in a DIV 3619 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { 3620 node.wrap(self.filterNode(new Node('div', 1))); 3621 } else { 3622 // We failed wrapping it, then remove or unwrap it 3623 if (node.name === 'style' || node.name === 'script') 3624 node.empty().remove(); 3625 else 3626 node.unwrap(); 3627 } 3628 } 3629 } 3630 }; 3631 3632 self.filterNode = function(node) { 3633 var i, name, list; 3634 3635 // Run element filters 3636 if (name in nodeFilters) { 3637 list = matchedNodes[name]; 3638 3639 if (list) 3640 list.push(node); 3641 else 3642 matchedNodes[name] = [node]; 3643 } 3644 3645 // Run attribute filters 3646 i = attributeFilters.length; 3647 while (i--) { 3648 name = attributeFilters[i].name; 3649 3650 if (name in node.attributes.map) { 3651 list = matchedAttributes[name]; 3652 3653 if (list) 3654 list.push(node); 3655 else 3656 matchedAttributes[name] = [node]; 3657 } 3658 } 3659 3660 return node; 3661 }; 3662 3663 self.addNodeFilter = function(name, callback) { 3664 tinymce.each(tinymce.explode(name), function(name) { 3665 var list = nodeFilters[name]; 3666 3667 if (!list) 3668 nodeFilters[name] = list = []; 3669 3670 list.push(callback); 3671 }); 3672 }; 3673 3674 self.addAttributeFilter = function(name, callback) { 3675 tinymce.each(tinymce.explode(name), function(name) { 3676 var i; 3677 3678 for (i = 0; i < attributeFilters.length; i++) { 3679 if (attributeFilters[i].name === name) { 3680 attributeFilters[i].callbacks.push(callback); 3681 return; 3682 } 3683 } 3684 3685 attributeFilters.push({name: name, callbacks: [callback]}); 3686 }); 3687 }; 3688 3689 self.parse = function(html, args) { 3690 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate, 3691 blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement, 3692 endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; 3693 3694 args = args || {}; 3695 matchedNodes = {}; 3696 matchedAttributes = {}; 3697 blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); 3698 nonEmptyElements = schema.getNonEmptyElements(); 3699 children = schema.children; 3700 validate = settings.validate; 3701 rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; 3702 3703 whiteSpaceElements = schema.getWhiteSpaceElements(); 3704 startWhiteSpaceRegExp = /^[ \t\r\n]+/; 3705 endWhiteSpaceRegExp = /[ \t\r\n]+$/; 3706 allWhiteSpaceRegExp = /[ \t\r\n]+/g; 3707 isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/; 3708 3709 function addRootBlocks() { 3710 var node = rootNode.firstChild, next, rootBlockNode; 3711 3712 while (node) { 3713 next = node.next; 3714 3715 if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) { 3716 if (!rootBlockNode) { 3717 // Create a new root block element 3718 rootBlockNode = createNode(rootBlockName, 1); 3719 rootNode.insert(rootBlockNode, node); 3720 rootBlockNode.append(node); 3721 } else 3722 rootBlockNode.append(node); 3723 } else { 3724 rootBlockNode = null; 3725 } 3726 3727 node = next; 3728 }; 3729 }; 3730 3731 function createNode(name, type) { 3732 var node = new Node(name, type), list; 3733 3734 if (name in nodeFilters) { 3735 list = matchedNodes[name]; 3736 3737 if (list) 3738 list.push(node); 3739 else 3740 matchedNodes[name] = [node]; 3741 } 3742 3743 return node; 3744 }; 3745 3746 function removeWhitespaceBefore(node) { 3747 var textNode, textVal, sibling; 3748 3749 for (textNode = node.prev; textNode && textNode.type === 3; ) { 3750 textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); 3751 3752 if (textVal.length > 0) { 3753 textNode.value = textVal; 3754 textNode = textNode.prev; 3755 } else { 3756 sibling = textNode.prev; 3757 textNode.remove(); 3758 textNode = sibling; 3759 } 3760 } 3761 }; 3762 3763 function cloneAndExcludeBlocks(input) { 3764 var name, output = {}; 3765 3766 for (name in input) { 3767 if (name !== 'li' && name != 'p') { 3768 output[name] = input[name]; 3769 } 3770 } 3771 3772 return output; 3773 }; 3774 3775 parser = new tinymce.html.SaxParser({ 3776 validate : validate, 3777 3778 // Exclude P and LI from DOM parsing since it's treated better by the DOM parser 3779 self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()), 3780 3781 cdata: function(text) { 3782 node.append(createNode('#cdata', 4)).value = text; 3783 }, 3784 3785 text: function(text, raw) { 3786 var textNode; 3787 3788 // Trim all redundant whitespace on non white space elements 3789 if (!isInWhiteSpacePreservedElement) { 3790 text = text.replace(allWhiteSpaceRegExp, ' '); 3791 3792 if (node.lastChild && blockElements[node.lastChild.name]) 3793 text = text.replace(startWhiteSpaceRegExp, ''); 3794 } 3795 3796 // Do we need to create the node 3797 if (text.length !== 0) { 3798 textNode = createNode('#text', 3); 3799 textNode.raw = !!raw; 3800 node.append(textNode).value = text; 3801 } 3802 }, 3803 3804 comment: function(text) { 3805 node.append(createNode('#comment', 8)).value = text; 3806 }, 3807 3808 pi: function(name, text) { 3809 node.append(createNode(name, 7)).value = text; 3810 removeWhitespaceBefore(node); 3811 }, 3812 3813 doctype: function(text) { 3814 var newNode; 3815 3816 newNode = node.append(createNode('#doctype', 10)); 3817 newNode.value = text; 3818 removeWhitespaceBefore(node); 3819 }, 3820 3821 start: function(name, attrs, empty) { 3822 var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent; 3823 3824 elementRule = validate ? schema.getElementRule(name) : {}; 3825 if (elementRule) { 3826 newNode = createNode(elementRule.outputName || name, 1); 3827 newNode.attributes = attrs; 3828 newNode.shortEnded = empty; 3829 3830 node.append(newNode); 3831 3832 // Check if node is valid child of the parent node is the child is 3833 // unknown we don't collect it since it's probably a custom element 3834 parent = children[node.name]; 3835 if (parent && children[newNode.name] && !parent[newNode.name]) 3836 invalidChildren.push(newNode); 3837 3838 attrFiltersLen = attributeFilters.length; 3839 while (attrFiltersLen--) { 3840 attrName = attributeFilters[attrFiltersLen].name; 3841 3842 if (attrName in attrs.map) { 3843 list = matchedAttributes[attrName]; 3844 3845 if (list) 3846 list.push(newNode); 3847 else 3848 matchedAttributes[attrName] = [newNode]; 3849 } 3850 } 3851 3852 // Trim whitespace before block 3853 if (blockElements[name]) 3854 removeWhitespaceBefore(newNode); 3855 3856 // Change current node if the element wasn't empty i.e not <br /> or <img /> 3857 if (!empty) 3858 node = newNode; 3859 3860 // Check if we are inside a whitespace preserved element 3861 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 3862 isInWhiteSpacePreservedElement = true; 3863 } 3864 } 3865 }, 3866 3867 end: function(name) { 3868 var textNode, elementRule, text, sibling, tempNode; 3869 3870 elementRule = validate ? schema.getElementRule(name) : {}; 3871 if (elementRule) { 3872 if (blockElements[name]) { 3873 if (!isInWhiteSpacePreservedElement) { 3874 // Trim whitespace of the first node in a block 3875 textNode = node.firstChild; 3876 if (textNode && textNode.type === 3) { 3877 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 3878 3879 // Any characters left after trim or should we remove it 3880 if (text.length > 0) { 3881 textNode.value = text; 3882 textNode = textNode.next; 3883 } else { 3884 sibling = textNode.next; 3885 textNode.remove(); 3886 textNode = sibling; 3887 } 3888 3889 // Remove any pure whitespace siblings 3890 while (textNode && textNode.type === 3) { 3891 text = textNode.value; 3892 sibling = textNode.next; 3893 3894 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 3895 textNode.remove(); 3896 textNode = sibling; 3897 } 3898 3899 textNode = sibling; 3900 } 3901 } 3902 3903 // Trim whitespace of the last node in a block 3904 textNode = node.lastChild; 3905 if (textNode && textNode.type === 3) { 3906 text = textNode.value.replace(endWhiteSpaceRegExp, ''); 3907 3908 // Any characters left after trim or should we remove it 3909 if (text.length > 0) { 3910 textNode.value = text; 3911 textNode = textNode.prev; 3912 } else { 3913 sibling = textNode.prev; 3914 textNode.remove(); 3915 textNode = sibling; 3916 } 3917 3918 // Remove any pure whitespace siblings 3919 while (textNode && textNode.type === 3) { 3920 text = textNode.value; 3921 sibling = textNode.prev; 3922 3923 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 3924 textNode.remove(); 3925 textNode = sibling; 3926 } 3927 3928 textNode = sibling; 3929 } 3930 } 3931 } 3932 3933 // Trim start white space 3934 textNode = node.prev; 3935 if (textNode && textNode.type === 3) { 3936 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 3937 3938 if (text.length > 0) 3939 textNode.value = text; 3940 else 3941 textNode.remove(); 3942 } 3943 } 3944 3945 // Check if we exited a whitespace preserved element 3946 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 3947 isInWhiteSpacePreservedElement = false; 3948 } 3949 3950 // Handle empty nodes 3951 if (elementRule.removeEmpty || elementRule.paddEmpty) { 3952 if (node.isEmpty(nonEmptyElements)) { 3953 if (elementRule.paddEmpty) 3954 node.empty().append(new Node('#text', '3')).value = '\u00a0'; 3955 else { 3956 // Leave nodes that have a name like <a name="name"> 3957 if (!node.attributes.map.name && !node.attributes.map.id) { 3958 tempNode = node.parent; 3959 node.empty().remove(); 3960 node = tempNode; 3961 return; 3962 } 3963 } 3964 } 3965 } 3966 3967 node = node.parent; 3968 } 3969 } 3970 }, schema); 3971 3972 rootNode = node = new Node(args.context || settings.root_name, 11); 3973 3974 parser.parse(html); 3975 3976 // Fix invalid children or report invalid children in a contextual parsing 3977 if (validate && invalidChildren.length) { 3978 if (!args.context) 3979 fixInvalidChildren(invalidChildren); 3980 else 3981 args.invalid = true; 3982 } 3983 3984 // Wrap nodes in the root into block elements if the root is body 3985 if (rootBlockName && rootNode.name == 'body') 3986 addRootBlocks(); 3987 3988 // Run filters only when the contents is valid 3989 if (!args.invalid) { 3990 // Run node filters 3991 for (name in matchedNodes) { 3992 list = nodeFilters[name]; 3993 nodes = matchedNodes[name]; 3994 3995 // Remove already removed children 3996 fi = nodes.length; 3997 while (fi--) { 3998 if (!nodes[fi].parent) 3999 nodes.splice(fi, 1); 4000 } 4001 4002 for (i = 0, l = list.length; i < l; i++) 4003 list[i](nodes, name, args); 4004 } 4005 4006 // Run attribute filters 4007 for (i = 0, l = attributeFilters.length; i < l; i++) { 4008 list = attributeFilters[i]; 4009 4010 if (list.name in matchedAttributes) { 4011 nodes = matchedAttributes[list.name]; 4012 4013 // Remove already removed children 4014 fi = nodes.length; 4015 while (fi--) { 4016 if (!nodes[fi].parent) 4017 nodes.splice(fi, 1); 4018 } 4019 4020 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) 4021 list.callbacks[fi](nodes, list.name, args); 4022 } 4023 } 4024 } 4025 4026 return rootNode; 4027 }; 4028 4029 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to 4030 // make it possible to place the caret inside empty blocks. This logic tries to remove 4031 // these elements and keep br elements that where intended to be there intact 4032 if (settings.remove_trailing_brs) { 4033 self.addNodeFilter('br', function(nodes, name) { 4034 var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()), 4035 nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName; 4036 4037 // Remove brs from body element as well 4038 blockElements.body = 1; 4039 4040 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p> 4041 for (i = 0; i < l; i++) { 4042 node = nodes[i]; 4043 parent = node.parent; 4044 4045 if (blockElements[node.parent.name] && node === parent.lastChild) { 4046 // Loop all nodes to the left of the current node and check for other BR elements 4047 // excluding bookmarks since they are invisible 4048 prev = node.prev; 4049 while (prev) { 4050 prevName = prev.name; 4051 4052 // Ignore bookmarks 4053 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { 4054 // Found a non BR element 4055 if (prevName !== "br") 4056 break; 4057 4058 // Found another br it's a <br><br> structure then don't remove anything 4059 if (prevName === 'br') { 4060 node = null; 4061 break; 4062 } 4063 } 4064 4065 prev = prev.prev; 4066 } 4067 4068 if (node) { 4069 node.remove(); 4070 4071 // Is the parent to be considered empty after we removed the BR 4072 if (parent.isEmpty(nonEmptyElements)) { 4073 elementRule = schema.getElementRule(parent.name); 4074 4075 // Remove or padd the element depending on schema rule 4076 if (elementRule) { 4077 if (elementRule.removeEmpty) 4078 parent.remove(); 4079 else if (elementRule.paddEmpty) 4080 parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0'; 4081 } 4082 } 4083 } 4084 } else { 4085 // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 4086 lastParent = node; 4087 while (parent.firstChild === lastParent && parent.lastChild === lastParent) { 4088 lastParent = parent; 4089 4090 if (blockElements[parent.name]) { 4091 break; 4092 } 4093 4094 parent = parent.parent; 4095 } 4096 4097 if (lastParent === parent) { 4098 textNode = new tinymce.html.Node('#text', 3); 4099 textNode.value = '\u00a0'; 4100 node.replace(textNode); 4101 } 4102 } 4103 } 4104 }); 4105 } 4106 4107 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. 4108 if (!settings.allow_html_in_named_anchor) { 4109 self.addAttributeFilter('id,name', function(nodes, name) { 4110 var i = nodes.length, sibling, prevSibling, parent, node; 4111 4112 while (i--) { 4113 node = nodes[i]; 4114 if (node.name === 'a' && node.firstChild && !node.attr('href')) { 4115 parent = node.parent; 4116 4117 // Move children after current node 4118 sibling = node.lastChild; 4119 do { 4120 prevSibling = sibling.prev; 4121 parent.insert(sibling, node); 4122 sibling = prevSibling; 4123 } while (sibling); 4124 } 4125 } 4126 }); 4127 } 4128 } 4129 })(tinymce); 4130 4131 tinymce.html.Writer = function(settings) { 4132 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; 4133 4134 settings = settings || {}; 4135 indent = settings.indent; 4136 indentBefore = tinymce.makeMap(settings.indent_before || ''); 4137 indentAfter = tinymce.makeMap(settings.indent_after || ''); 4138 encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); 4139 htmlOutput = settings.element_format == "html"; 4140 4141 return { 4142 start: function(name, attrs, empty) { 4143 var i, l, attr, value; 4144 4145 if (indent && indentBefore[name] && html.length > 0) { 4146 value = html[html.length - 1]; 4147 4148 if (value.length > 0 && value !== '\n') 4149 html.push('\n'); 4150 } 4151 4152 html.push('<', name); 4153 4154 if (attrs) { 4155 for (i = 0, l = attrs.length; i < l; i++) { 4156 attr = attrs[i]; 4157 html.push(' ', attr.name, '="', encode(attr.value, true), '"'); 4158 } 4159 } 4160 4161 if (!empty || htmlOutput) 4162 html[html.length] = '>'; 4163 else 4164 html[html.length] = ' />'; 4165 4166 if (empty && indent && indentAfter[name] && html.length > 0) { 4167 value = html[html.length - 1]; 4168 4169 if (value.length > 0 && value !== '\n') 4170 html.push('\n'); 4171 } 4172 }, 4173 4174 end: function(name) { 4175 var value; 4176 4177 /*if (indent && indentBefore[name] && html.length > 0) { 4178 value = html[html.length - 1]; 4179 4180 if (value.length > 0 && value !== '\n') 4181 html.push('\n'); 4182 }*/ 4183 4184 html.push('</', name, '>'); 4185 4186 if (indent && indentAfter[name] && html.length > 0) { 4187 value = html[html.length - 1]; 4188 4189 if (value.length > 0 && value !== '\n') 4190 html.push('\n'); 4191 } 4192 }, 4193 4194 text: function(text, raw) { 4195 if (text.length > 0) 4196 html[html.length] = raw ? text : encode(text); 4197 }, 4198 4199 cdata: function(text) { 4200 html.push('<![CDATA[', text, ']]>'); 4201 }, 4202 4203 comment: function(text) { 4204 html.push('<!--', text, '-->'); 4205 }, 4206 4207 pi: function(name, text) { 4208 if (text) 4209 html.push('<?', name, ' ', text, '?>'); 4210 else 4211 html.push('<?', name, '?>'); 4212 4213 if (indent) 4214 html.push('\n'); 4215 }, 4216 4217 doctype: function(text) { 4218 html.push('<!DOCTYPE', text, '>', indent ? '\n' : ''); 4219 }, 4220 4221 reset: function() { 4222 html.length = 0; 4223 }, 4224 4225 getContent: function() { 4226 return html.join('').replace(/\n$/, ''); 4227 } 4228 }; 4229 }; 4230 4231 (function(tinymce) { 4232 tinymce.html.Serializer = function(settings, schema) { 4233 var self = this, writer = new tinymce.html.Writer(settings); 4234 4235 settings = settings || {}; 4236 settings.validate = "validate" in settings ? settings.validate : true; 4237 4238 self.schema = schema = schema || new tinymce.html.Schema(); 4239 self.writer = writer; 4240 4241 self.serialize = function(node) { 4242 var handlers, validate; 4243 4244 validate = settings.validate; 4245 4246 handlers = { 4247 // #text 4248 3: function(node, raw) { 4249 writer.text(node.value, node.raw); 4250 }, 4251 4252 // #comment 4253 8: function(node) { 4254 writer.comment(node.value); 4255 }, 4256 4257 // Processing instruction 4258 7: function(node) { 4259 writer.pi(node.name, node.value); 4260 }, 4261 4262 // Doctype 4263 10: function(node) { 4264 writer.doctype(node.value); 4265 }, 4266 4267 // CDATA 4268 4: function(node) { 4269 writer.cdata(node.value); 4270 }, 4271 4272 // Document fragment 4273 11: function(node) { 4274 if ((node = node.firstChild)) { 4275 do { 4276 walk(node); 4277 } while (node = node.next); 4278 } 4279 } 4280 }; 4281 4282 writer.reset(); 4283 4284 function walk(node) { 4285 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; 4286 4287 if (!handler) { 4288 name = node.name; 4289 isEmpty = node.shortEnded; 4290 attrs = node.attributes; 4291 4292 // Sort attributes 4293 if (validate && attrs && attrs.length > 1) { 4294 sortedAttrs = []; 4295 sortedAttrs.map = {}; 4296 4297 elementRule = schema.getElementRule(node.name); 4298 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { 4299 attrName = elementRule.attributesOrder[i]; 4300 4301 if (attrName in attrs.map) { 4302 attrValue = attrs.map[attrName]; 4303 sortedAttrs.map[attrName] = attrValue; 4304 sortedAttrs.push({name: attrName, value: attrValue}); 4305 } 4306 } 4307 4308 for (i = 0, l = attrs.length; i < l; i++) { 4309 attrName = attrs[i].name; 4310 4311 if (!(attrName in sortedAttrs.map)) { 4312 attrValue = attrs.map[attrName]; 4313 sortedAttrs.map[attrName] = attrValue; 4314 sortedAttrs.push({name: attrName, value: attrValue}); 4315 } 4316 } 4317 4318 attrs = sortedAttrs; 4319 } 4320 4321 writer.start(node.name, attrs, isEmpty); 4322 4323 if (!isEmpty) { 4324 if ((node = node.firstChild)) { 4325 do { 4326 walk(node); 4327 } while (node = node.next); 4328 } 4329 4330 writer.end(name); 4331 } 4332 } else 4333 handler(node); 4334 } 4335 4336 // Serialize element and treat all non elements as fragments 4337 if (node.type == 1 && !settings.inner) 4338 walk(node); 4339 else 4340 handlers[11](node); 4341 4342 return writer.getContent(); 4343 }; 4344 } 4345 })(tinymce); 4346 4347 // JSLint defined globals 4348 /*global tinymce:false, window:false */ 4349 4350 tinymce.dom = {}; 4351 4352 (function(namespace, expando) { 4353 var w3cEventModel = !!document.addEventListener; 4354 4355 function addEvent(target, name, callback, capture) { 4356 if (target.addEventListener) { 4357 target.addEventListener(name, callback, capture || false); 4358 } else if (target.attachEvent) { 4359 target.attachEvent('on' + name, callback); 4360 } 4361 } 4362 4363 function removeEvent(target, name, callback, capture) { 4364 if (target.removeEventListener) { 4365 target.removeEventListener(name, callback, capture || false); 4366 } else if (target.detachEvent) { 4367 target.detachEvent('on' + name, callback); 4368 } 4369 } 4370 4371 function fix(original_event, data) { 4372 var name, event = data || {}; 4373 4374 // Dummy function that gets replaced on the delegation state functions 4375 function returnFalse() { 4376 return false; 4377 } 4378 4379 // Dummy function that gets replaced on the delegation state functions 4380 function returnTrue() { 4381 return true; 4382 } 4383 4384 // Copy all properties from the original event 4385 for (name in original_event) { 4386 // layerX/layerY is deprecated in Chrome and produces a warning 4387 if (name !== "layerX" && name !== "layerY") { 4388 event[name] = original_event[name]; 4389 } 4390 } 4391 4392 // Normalize target IE uses srcElement 4393 if (!event.target) { 4394 event.target = event.srcElement || document; 4395 } 4396 4397 // Add preventDefault method 4398 event.preventDefault = function() { 4399 event.isDefaultPrevented = returnTrue; 4400 4401 // Execute preventDefault on the original event object 4402 if (original_event) { 4403 if (original_event.preventDefault) { 4404 original_event.preventDefault(); 4405 } else { 4406 original_event.returnValue = false; // IE 4407 } 4408 } 4409 }; 4410 4411 // Add stopPropagation 4412 event.stopPropagation = function() { 4413 event.isPropagationStopped = returnTrue; 4414 4415 // Execute stopPropagation on the original event object 4416 if (original_event) { 4417 if (original_event.stopPropagation) { 4418 original_event.stopPropagation(); 4419 } else { 4420 original_event.cancelBubble = true; // IE 4421 } 4422 } 4423 }; 4424 4425 // Add stopImmediatePropagation 4426 event.stopImmediatePropagation = function() { 4427 event.isImmediatePropagationStopped = returnTrue; 4428 event.stopPropagation(); 4429 }; 4430 4431 // Add event delegation states 4432 if (!event.isDefaultPrevented) { 4433 event.isDefaultPrevented = returnFalse; 4434 event.isPropagationStopped = returnFalse; 4435 event.isImmediatePropagationStopped = returnFalse; 4436 } 4437 4438 return event; 4439 } 4440 4441 function bindOnReady(win, callback, event_utils) { 4442 var doc = win.document, event = {type: 'ready'}; 4443 4444 // Gets called when the DOM is ready 4445 function readyHandler() { 4446 if (!event_utils.domLoaded) { 4447 event_utils.domLoaded = true; 4448 callback(event); 4449 } 4450 } 4451 4452 // Use W3C method 4453 if (w3cEventModel) { 4454 addEvent(win, 'DOMContentLoaded', readyHandler); 4455 } else { 4456 // Use IE method 4457 addEvent(doc, "readystatechange", function() { 4458 if (doc.readyState === "complete") { 4459 removeEvent(doc, "readystatechange", arguments.callee); 4460 readyHandler(); 4461 } 4462 }); 4463 4464 // Wait until we can scroll, when we can the DOM is initialized 4465 if (doc.documentElement.doScroll && win === win.top) { 4466 (function() { 4467 try { 4468 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. 4469 // http://javascript.nwbox.com/IEContentLoaded/ 4470 doc.documentElement.doScroll("left"); 4471 } catch (ex) { 4472 setTimeout(arguments.callee, 0); 4473 return; 4474 } 4475 4476 readyHandler(); 4477 })(); 4478 } 4479 } 4480 4481 // Fallback if any of the above methods should fail for some odd reason 4482 addEvent(win, 'load', readyHandler); 4483 } 4484 4485 function EventUtils(proxy) { 4486 var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave; 4487 4488 hasMouseEnterLeave = "onmouseenter" in document.documentElement; 4489 hasFocusIn = "onfocusin" in document.documentElement; 4490 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; 4491 count = 1; 4492 4493 // State if the DOMContentLoaded was executed or not 4494 self.domLoaded = false; 4495 self.events = events; 4496 4497 function executeHandlers(evt, id) { 4498 var callbackList, i, l, callback; 4499 4500 callbackList = events[id][evt.type]; 4501 if (callbackList) { 4502 for (i = 0, l = callbackList.length; i < l; i++) { 4503 callback = callbackList[i]; 4504 4505 // Check if callback exists might be removed if a unbind is called inside the callback 4506 if (callback && callback.func.call(callback.scope, evt) === false) { 4507 evt.preventDefault(); 4508 } 4509 4510 // Should we stop propagation to immediate listeners 4511 if (evt.isImmediatePropagationStopped()) { 4512 return; 4513 } 4514 } 4515 } 4516 } 4517 4518 self.bind = function(target, names, callback, scope) { 4519 var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window; 4520 4521 // Native event handler function patches the event and executes the callbacks for the expando 4522 function defaultNativeHandler(evt) { 4523 executeHandlers(fix(evt || win.event), id); 4524 } 4525 4526 // Don't bind to text nodes or comments 4527 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4528 return; 4529 } 4530 4531 // Create or get events id for the target 4532 if (!target[expando]) { 4533 id = count++; 4534 target[expando] = id; 4535 events[id] = {}; 4536 } else { 4537 id = target[expando]; 4538 4539 if (!events[id]) { 4540 events[id] = {}; 4541 } 4542 } 4543 4544 // Setup the specified scope or use the target as a default 4545 scope = scope || target; 4546 4547 // Split names and bind each event, enables you to bind multiple events with one call 4548 names = names.split(' '); 4549 i = names.length; 4550 while (i--) { 4551 name = names[i]; 4552 nativeHandler = defaultNativeHandler; 4553 fakeName = capture = false; 4554 4555 // Use ready instead of DOMContentLoaded 4556 if (name === "DOMContentLoaded") { 4557 name = "ready"; 4558 } 4559 4560 // DOM is already ready 4561 if ((self.domLoaded || target.readyState == 'complete') && name === "ready") { 4562 self.domLoaded = true; 4563 callback.call(scope, fix({type: name})); 4564 continue; 4565 } 4566 4567 // Handle mouseenter/mouseleaver 4568 if (!hasMouseEnterLeave) { 4569 fakeName = mouseEnterLeave[name]; 4570 4571 if (fakeName) { 4572 nativeHandler = function(evt) { 4573 var current, related; 4574 4575 current = evt.currentTarget; 4576 related = evt.relatedTarget; 4577 4578 // Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element 4579 if (related && current.contains) { 4580 // Use contains for performance 4581 related = current.contains(related); 4582 } else { 4583 while (related && related !== current) { 4584 related = related.parentNode; 4585 } 4586 } 4587 4588 // Fire fake event 4589 if (!related) { 4590 evt = fix(evt || win.event); 4591 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'; 4592 evt.target = current; 4593 executeHandlers(evt, id); 4594 } 4595 }; 4596 } 4597 } 4598 4599 // Fake bubbeling of focusin/focusout 4600 if (!hasFocusIn && (name === "focusin" || name === "focusout")) { 4601 capture = true; 4602 fakeName = name === "focusin" ? "focus" : "blur"; 4603 nativeHandler = function(evt) { 4604 evt = fix(evt || win.event); 4605 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'; 4606 executeHandlers(evt, id); 4607 }; 4608 } 4609 4610 // Setup callback list and bind native event 4611 callbackList = events[id][name]; 4612 if (!callbackList) { 4613 events[id][name] = callbackList = [{func: callback, scope: scope}]; 4614 callbackList.fakeName = fakeName; 4615 callbackList.capture = capture; 4616 4617 // Add the nativeHandler to the callback list so that we can later unbind it 4618 callbackList.nativeHandler = nativeHandler; 4619 if (!w3cEventModel) { 4620 callbackList.proxyHandler = proxy(id); 4621 } 4622 4623 // Check if the target has native events support 4624 if (name === "ready") { 4625 bindOnReady(target, nativeHandler, self); 4626 } else { 4627 addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture); 4628 } 4629 } else { 4630 // If it already has an native handler then just push the callback 4631 callbackList.push({func: callback, scope: scope}); 4632 } 4633 } 4634 4635 target = callbackList = 0; // Clean memory for IE 4636 4637 return callback; 4638 }; 4639 4640 self.unbind = function(target, names, callback) { 4641 var id, callbackList, i, ci, name, eventMap; 4642 4643 // Don't bind to text nodes or comments 4644 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4645 return self; 4646 } 4647 4648 // Unbind event or events if the target has the expando 4649 id = target[expando]; 4650 if (id) { 4651 eventMap = events[id]; 4652 4653 // Specific callback 4654 if (names) { 4655 names = names.split(' '); 4656 i = names.length; 4657 while (i--) { 4658 name = names[i]; 4659 callbackList = eventMap[name]; 4660 4661 // Unbind the event if it exists in the map 4662 if (callbackList) { 4663 // Remove specified callback 4664 if (callback) { 4665 ci = callbackList.length; 4666 while (ci--) { 4667 if (callbackList[ci].func === callback) { 4668 callbackList.splice(ci, 1); 4669 } 4670 } 4671 } 4672 4673 // Remove all callbacks if there isn't a specified callback or there is no callbacks left 4674 if (!callback || callbackList.length === 0) { 4675 delete eventMap[name]; 4676 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4677 } 4678 } 4679 } 4680 } else { 4681 // All events for a specific element 4682 for (name in eventMap) { 4683 callbackList = eventMap[name]; 4684 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4685 } 4686 4687 eventMap = {}; 4688 } 4689 4690 // Check if object is empty, if it isn't then we won't remove the expando map 4691 for (name in eventMap) { 4692 return self; 4693 } 4694 4695 // Delete event object 4696 delete events[id]; 4697 4698 // Remove expando from target 4699 try { 4700 // IE will fail here since it can't delete properties from window 4701 delete target[expando]; 4702 } catch (ex) { 4703 // IE will set it to null 4704 target[expando] = null; 4705 } 4706 } 4707 4708 return self; 4709 }; 4710 4711 self.fire = function(target, name, args) { 4712 var id, event; 4713 4714 // Don't bind to text nodes or comments 4715 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4716 return self; 4717 } 4718 4719 // Build event object by patching the args 4720 event = fix(null, args); 4721 event.type = name; 4722 4723 do { 4724 // Found an expando that means there is listeners to execute 4725 id = target[expando]; 4726 if (id) { 4727 executeHandlers(event, id); 4728 } 4729 4730 // Walk up the DOM 4731 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow; 4732 } while (target && !event.isPropagationStopped()); 4733 4734 return self; 4735 }; 4736 4737 self.clean = function(target) { 4738 var i, children, unbind = self.unbind; 4739 4740 // Don't bind to text nodes or comments 4741 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4742 return self; 4743 } 4744 4745 // Unbind any element on the specificed target 4746 if (target[expando]) { 4747 unbind(target); 4748 } 4749 4750 // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children 4751 if (!target.getElementsByTagName) { 4752 target = target.document; 4753 } 4754 4755 // Remove events from each child element 4756 if (target && target.getElementsByTagName) { 4757 unbind(target); 4758 4759 children = target.getElementsByTagName('*'); 4760 i = children.length; 4761 while (i--) { 4762 target = children[i]; 4763 4764 if (target[expando]) { 4765 unbind(target); 4766 } 4767 } 4768 } 4769 4770 return self; 4771 }; 4772 4773 self.callNativeHandler = function(id, evt) { 4774 if (events) { 4775 events[id][evt.type].nativeHandler(evt); 4776 } 4777 }; 4778 4779 self.destory = function() { 4780 events = {}; 4781 }; 4782 4783 // Legacy function calls 4784 4785 self.add = function(target, events, func, scope) { 4786 // Old API supported direct ID assignment 4787 if (typeof(target) === "string") { 4788 target = document.getElementById(target); 4789 } 4790 4791 // Old API supported multiple targets 4792 if (target && target instanceof Array) { 4793 var i = target.length; 4794 4795 while (i--) { 4796 self.add(target[i], events, func, scope); 4797 } 4798 4799 return; 4800 } 4801 4802 // Old API called ready init 4803 if (events === "init") { 4804 events = "ready"; 4805 } 4806 4807 return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope); 4808 }; 4809 4810 self.remove = function(target, events, func, scope) { 4811 if (!target) { 4812 return self; 4813 } 4814 4815 // Old API supported direct ID assignment 4816 if (typeof(target) === "string") { 4817 target = document.getElementById(target); 4818 } 4819 4820 // Old API supported multiple targets 4821 if (target instanceof Array) { 4822 var i = target.length; 4823 4824 while (i--) { 4825 self.remove(target[i], events, func, scope); 4826 } 4827 4828 return self; 4829 } 4830 4831 return self.unbind(target, events instanceof Array ? events.join(' ') : events, func); 4832 }; 4833 4834 self.clear = function(target) { 4835 // Old API supported direct ID assignment 4836 if (typeof(target) === "string") { 4837 target = document.getElementById(target); 4838 } 4839 4840 return self.clean(target); 4841 }; 4842 4843 self.cancel = function(e) { 4844 if (e) { 4845 self.prevent(e); 4846 self.stop(e); 4847 } 4848 4849 return false; 4850 }; 4851 4852 self.prevent = function(e) { 4853 if (!e.preventDefault) { 4854 e = fix(e); 4855 } 4856 4857 e.preventDefault(); 4858 4859 return false; 4860 }; 4861 4862 self.stop = function(e) { 4863 if (!e.stopPropagation) { 4864 e = fix(e); 4865 } 4866 4867 e.stopPropagation(); 4868 4869 return false; 4870 }; 4871 } 4872 4873 namespace.EventUtils = EventUtils; 4874 4875 namespace.Event = new EventUtils(function(id) { 4876 return function(evt) { 4877 tinymce.dom.Event.callNativeHandler(id, evt); 4878 }; 4879 }); 4880 4881 // Bind ready event when tinymce script is loaded 4882 namespace.Event.bind(window, 'ready', function() {}); 4883 4884 namespace = 0; 4885 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando 4886 4887 tinymce.dom.TreeWalker = function(start_node, root_node) { 4888 var node = start_node; 4889 4890 function findSibling(node, start_name, sibling_name, shallow) { 4891 var sibling, parent; 4892 4893 if (node) { 4894 // Walk into nodes if it has a start 4895 if (!shallow && node[start_name]) 4896 return node[start_name]; 4897 4898 // Return the sibling if it has one 4899 if (node != root_node) { 4900 sibling = node[sibling_name]; 4901 if (sibling) 4902 return sibling; 4903 4904 // Walk up the parents to look for siblings 4905 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) { 4906 sibling = parent[sibling_name]; 4907 if (sibling) 4908 return sibling; 4909 } 4910 } 4911 } 4912 }; 4913 4914 this.current = function() { 4915 return node; 4916 }; 4917 4918 this.next = function(shallow) { 4919 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); 4920 }; 4921 4922 this.prev = function(shallow) { 4923 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow)); 4924 }; 4925 }; 4926 4927 (function(tinymce) { 4928 // Shorten names 4929 var each = tinymce.each, 4930 is = tinymce.is, 4931 isWebKit = tinymce.isWebKit, 4932 isIE = tinymce.isIE, 4933 Entities = tinymce.html.Entities, 4934 simpleSelectorRe = /^([a-z0-9],?)+$/i, 4935 whiteSpaceRegExp = /^[ \t\r\n]*$/; 4936 4937 tinymce.create('tinymce.dom.DOMUtils', { 4938 doc : null, 4939 root : null, 4940 files : null, 4941 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, 4942 props : { 4943 "for" : "htmlFor", 4944 "class" : "className", 4945 className : "className", 4946 checked : "checked", 4947 disabled : "disabled", 4948 maxlength : "maxLength", 4949 readonly : "readOnly", 4950 selected : "selected", 4951 value : "value", 4952 id : "id", 4953 name : "name", 4954 type : "type" 4955 }, 4956 4957 DOMUtils : function(d, s) { 4958 var t = this, globalStyle, name, blockElementsMap; 4959 4960 t.doc = d; 4961 t.win = window; 4962 t.files = {}; 4963 t.cssFlicker = false; 4964 t.counter = 0; 4965 t.stdMode = !tinymce.isIE || d.documentMode >= 8; 4966 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode; 4967 t.hasOuterHTML = "outerHTML" in d.createElement("a"); 4968 4969 t.settings = s = tinymce.extend({ 4970 keep_values : false, 4971 hex_colors : 1 4972 }, s); 4973 4974 t.schema = s.schema; 4975 t.styles = new tinymce.html.Styles({ 4976 url_converter : s.url_converter, 4977 url_converter_scope : s.url_converter_scope 4978 }, s.schema); 4979 4980 // Fix IE6SP2 flicker and check it failed for pre SP2 4981 if (tinymce.isIE6) { 4982 try { 4983 d.execCommand('BackgroundImageCache', false, true); 4984 } catch (e) { 4985 t.cssFlicker = true; 4986 } 4987 } 4988 4989 t.fixDoc(d); 4990 t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event; 4991 tinymce.addUnload(t.destroy, t); 4992 blockElementsMap = s.schema ? s.schema.getBlockElements() : {}; 4993 4994 t.isBlock = function(node) { 4995 // This function is called in module pattern style since it might be executed with the wrong this scope 4996 var type = node.nodeType; 4997 4998 // If it's a node then check the type and use the nodeName 4999 if (type) 5000 return !!(type === 1 && blockElementsMap[node.nodeName]); 5001 5002 return !!blockElementsMap[node]; 5003 }; 5004 }, 5005 5006 fixDoc: function(doc) { 5007 var settings = this.settings, name; 5008 5009 if (isIE && settings.schema) { 5010 // Add missing HTML 4/5 elements to IE 5011 ('abbr article aside audio canvas ' + 5012 'details figcaption figure footer ' + 5013 'header hgroup mark menu meter nav ' + 5014 'output progress section summary ' + 5015 'time video').replace(/\w+/g, function(name) { 5016 doc.createElement(name); 5017 }); 5018 5019 // Create all custom elements 5020 for (name in settings.schema.getCustomElements()) { 5021 doc.createElement(name); 5022 } 5023 } 5024 }, 5025 5026 clone: function(node, deep) { 5027 var self = this, clone, doc; 5028 5029 // TODO: Add feature detection here in the future 5030 if (!isIE || node.nodeType !== 1 || deep) { 5031 return node.cloneNode(deep); 5032 } 5033 5034 doc = self.doc; 5035 5036 // Make a HTML5 safe shallow copy 5037 if (!deep) { 5038 clone = doc.createElement(node.nodeName); 5039 5040 // Copy attribs 5041 each(self.getAttribs(node), function(attr) { 5042 self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName)); 5043 }); 5044 5045 return clone; 5046 } 5047 /* 5048 // Setup HTML5 patched document fragment 5049 if (!self.frag) { 5050 self.frag = doc.createDocumentFragment(); 5051 self.fixDoc(self.frag); 5052 } 5053 5054 // Make a deep copy by adding it to the document fragment then removing it this removed the :section 5055 clone = doc.createElement('div'); 5056 self.frag.appendChild(clone); 5057 clone.innerHTML = node.outerHTML; 5058 self.frag.removeChild(clone); 5059 */ 5060 return clone.firstChild; 5061 }, 5062 5063 getRoot : function() { 5064 var t = this, s = t.settings; 5065 5066 return (s && t.get(s.root_element)) || t.doc.body; 5067 }, 5068 5069 getViewPort : function(w) { 5070 var d, b; 5071 5072 w = !w ? this.win : w; 5073 d = w.document; 5074 b = this.boxModel ? d.documentElement : d.body; 5075 5076 // Returns viewport size excluding scrollbars 5077 return { 5078 x : w.pageXOffset || b.scrollLeft, 5079 y : w.pageYOffset || b.scrollTop, 5080 w : w.innerWidth || b.clientWidth, 5081 h : w.innerHeight || b.clientHeight 5082 }; 5083 }, 5084 5085 getRect : function(e) { 5086 var p, t = this, sr; 5087 5088 e = t.get(e); 5089 p = t.getPos(e); 5090 sr = t.getSize(e); 5091 5092 return { 5093 x : p.x, 5094 y : p.y, 5095 w : sr.w, 5096 h : sr.h 5097 }; 5098 }, 5099 5100 getSize : function(e) { 5101 var t = this, w, h; 5102 5103 e = t.get(e); 5104 w = t.getStyle(e, 'width'); 5105 h = t.getStyle(e, 'height'); 5106 5107 // Non pixel value, then force offset/clientWidth 5108 if (w.indexOf('px') === -1) 5109 w = 0; 5110 5111 // Non pixel value, then force offset/clientWidth 5112 if (h.indexOf('px') === -1) 5113 h = 0; 5114 5115 return { 5116 w : parseInt(w, 10) || e.offsetWidth || e.clientWidth, 5117 h : parseInt(h, 10) || e.offsetHeight || e.clientHeight 5118 }; 5119 }, 5120 5121 getParent : function(n, f, r) { 5122 return this.getParents(n, f, r, false); 5123 }, 5124 5125 getParents : function(n, f, r, c) { 5126 var t = this, na, se = t.settings, o = []; 5127 5128 n = t.get(n); 5129 c = c === undefined; 5130 5131 if (se.strict_root) 5132 r = r || t.getRoot(); 5133 5134 // Wrap node name as func 5135 if (is(f, 'string')) { 5136 na = f; 5137 5138 if (f === '*') { 5139 f = function(n) {return n.nodeType == 1;}; 5140 } else { 5141 f = function(n) { 5142 return t.is(n, na); 5143 }; 5144 } 5145 } 5146 5147 while (n) { 5148 if (n == r || !n.nodeType || n.nodeType === 9) 5149 break; 5150 5151 if (!f || f(n)) { 5152 if (c) 5153 o.push(n); 5154 else 5155 return n; 5156 } 5157 5158 n = n.parentNode; 5159 } 5160 5161 return c ? o : null; 5162 }, 5163 5164 get : function(e) { 5165 var n; 5166 5167 if (e && this.doc && typeof(e) == 'string') { 5168 n = e; 5169 e = this.doc.getElementById(e); 5170 5171 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick 5172 if (e && e.id !== n) 5173 return this.doc.getElementsByName(n)[1]; 5174 } 5175 5176 return e; 5177 }, 5178 5179 getNext : function(node, selector) { 5180 return this._findSib(node, selector, 'nextSibling'); 5181 }, 5182 5183 getPrev : function(node, selector) { 5184 return this._findSib(node, selector, 'previousSibling'); 5185 }, 5186 5187 5188 select : function(pa, s) { 5189 var t = this; 5190 5191 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); 5192 }, 5193 5194 is : function(n, selector) { 5195 var i; 5196 5197 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance 5198 if (n.length === undefined) { 5199 // Simple all selector 5200 if (selector === '*') 5201 return n.nodeType == 1; 5202 5203 // Simple selector just elements 5204 if (simpleSelectorRe.test(selector)) { 5205 selector = selector.toLowerCase().split(/,/); 5206 n = n.nodeName.toLowerCase(); 5207 5208 for (i = selector.length - 1; i >= 0; i--) { 5209 if (selector[i] == n) 5210 return true; 5211 } 5212 5213 return false; 5214 } 5215 } 5216 5217 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; 5218 }, 5219 5220 5221 add : function(p, n, a, h, c) { 5222 var t = this; 5223 5224 return this.run(p, function(p) { 5225 var e, k; 5226 5227 e = is(n, 'string') ? t.doc.createElement(n) : n; 5228 t.setAttribs(e, a); 5229 5230 if (h) { 5231 if (h.nodeType) 5232 e.appendChild(h); 5233 else 5234 t.setHTML(e, h); 5235 } 5236 5237 return !c ? p.appendChild(e) : e; 5238 }); 5239 }, 5240 5241 create : function(n, a, h) { 5242 return this.add(this.doc.createElement(n), n, a, h, 1); 5243 }, 5244 5245 createHTML : function(n, a, h) { 5246 var o = '', t = this, k; 5247 5248 o += '<' + n; 5249 5250 for (k in a) { 5251 if (a.hasOwnProperty(k)) 5252 o += ' ' + k + '="' + t.encode(a[k]) + '"'; 5253 } 5254 5255 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime 5256 if (typeof(h) != "undefined") 5257 return o + '>' + h + '</' + n + '>'; 5258 5259 return o + ' />'; 5260 }, 5261 5262 remove : function(node, keep_children) { 5263 return this.run(node, function(node) { 5264 var child, parent = node.parentNode; 5265 5266 if (!parent) 5267 return null; 5268 5269 if (keep_children) { 5270 while (child = node.firstChild) { 5271 // IE 8 will crash if you don't remove completely empty text nodes 5272 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) 5273 parent.insertBefore(child, node); 5274 else 5275 node.removeChild(child); 5276 } 5277 } 5278 5279 return parent.removeChild(node); 5280 }); 5281 }, 5282 5283 setStyle : function(n, na, v) { 5284 var t = this; 5285 5286 return t.run(n, function(e) { 5287 var s, i; 5288 5289 s = e.style; 5290 5291 // Camelcase it, if needed 5292 na = na.replace(/-(\D)/g, function(a, b){ 5293 return b.toUpperCase(); 5294 }); 5295 5296 // Default px suffix on these 5297 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) 5298 v += 'px'; 5299 5300 switch (na) { 5301 case 'opacity': 5302 // IE specific opacity 5303 if (isIE) { 5304 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; 5305 5306 if (!n.currentStyle || !n.currentStyle.hasLayout) 5307 s.display = 'inline-block'; 5308 } 5309 5310 // Fix for older browsers 5311 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; 5312 break; 5313 5314 case 'float': 5315 isIE ? s.styleFloat = v : s.cssFloat = v; 5316 break; 5317 5318 default: 5319 s[na] = v || ''; 5320 } 5321 5322 // Force update of the style data 5323 if (t.settings.update_styles) 5324 t.setAttrib(e, 'data-mce-style'); 5325 }); 5326 }, 5327 5328 getStyle : function(n, na, c) { 5329 n = this.get(n); 5330 5331 if (!n) 5332 return; 5333 5334 // Gecko 5335 if (this.doc.defaultView && c) { 5336 // Remove camelcase 5337 na = na.replace(/[A-Z]/g, function(a){ 5338 return '-' + a; 5339 }); 5340 5341 try { 5342 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); 5343 } catch (ex) { 5344 // Old safari might fail 5345 return null; 5346 } 5347 } 5348 5349 // Camelcase it, if needed 5350 na = na.replace(/-(\D)/g, function(a, b){ 5351 return b.toUpperCase(); 5352 }); 5353 5354 if (na == 'float') 5355 na = isIE ? 'styleFloat' : 'cssFloat'; 5356 5357 // IE & Opera 5358 if (n.currentStyle && c) 5359 return n.currentStyle[na]; 5360 5361 return n.style ? n.style[na] : undefined; 5362 }, 5363 5364 setStyles : function(e, o) { 5365 var t = this, s = t.settings, ol; 5366 5367 ol = s.update_styles; 5368 s.update_styles = 0; 5369 5370 each(o, function(v, n) { 5371 t.setStyle(e, n, v); 5372 }); 5373 5374 // Update style info 5375 s.update_styles = ol; 5376 if (s.update_styles) 5377 t.setAttrib(e, s.cssText); 5378 }, 5379 5380 removeAllAttribs: function(e) { 5381 return this.run(e, function(e) { 5382 var i, attrs = e.attributes; 5383 for (i = attrs.length - 1; i >= 0; i--) { 5384 e.removeAttributeNode(attrs.item(i)); 5385 } 5386 }); 5387 }, 5388 5389 setAttrib : function(e, n, v) { 5390 var t = this; 5391 5392 // Whats the point 5393 if (!e || !n) 5394 return; 5395 5396 // Strict XML mode 5397 if (t.settings.strict) 5398 n = n.toLowerCase(); 5399 5400 return this.run(e, function(e) { 5401 var s = t.settings; 5402 var originalValue = e.getAttribute(n); 5403 if (v !== null) { 5404 switch (n) { 5405 case "style": 5406 if (!is(v, 'string')) { 5407 each(v, function(v, n) { 5408 t.setStyle(e, n, v); 5409 }); 5410 5411 return; 5412 } 5413 5414 // No mce_style for elements with these since they might get resized by the user 5415 if (s.keep_values) { 5416 if (v && !t._isRes(v)) 5417 e.setAttribute('data-mce-style', v, 2); 5418 else 5419 e.removeAttribute('data-mce-style', 2); 5420 } 5421 5422 e.style.cssText = v; 5423 break; 5424 5425 case "class": 5426 e.className = v || ''; // Fix IE null bug 5427 break; 5428 5429 case "src": 5430 case "href": 5431 if (s.keep_values) { 5432 if (s.url_converter) 5433 v = s.url_converter.call(s.url_converter_scope || t, v, n, e); 5434 5435 t.setAttrib(e, 'data-mce-' + n, v, 2); 5436 } 5437 5438 break; 5439 5440 case "shape": 5441 e.setAttribute('data-mce-style', v); 5442 break; 5443 } 5444 } 5445 if (is(v) && v !== null && v.length !== 0) 5446 e.setAttribute(n, '' + v, 2); 5447 else 5448 e.removeAttribute(n, 2); 5449 5450 // fire onChangeAttrib event for attributes that have changed 5451 if (tinyMCE.activeEditor && originalValue != v) { 5452 var ed = tinyMCE.activeEditor; 5453 ed.onSetAttrib.dispatch(ed, e, n, v); 5454 } 5455 }); 5456 }, 5457 5458 setAttribs : function(e, o) { 5459 var t = this; 5460 5461 return this.run(e, function(e) { 5462 each(o, function(v, n) { 5463 t.setAttrib(e, n, v); 5464 }); 5465 }); 5466 }, 5467 5468 getAttrib : function(e, n, dv) { 5469 var v, t = this, undef; 5470 5471 e = t.get(e); 5472 5473 if (!e || e.nodeType !== 1) 5474 return dv === undef ? false : dv; 5475 5476 if (!is(dv)) 5477 dv = ''; 5478 5479 // Try the mce variant for these 5480 if (/^(src|href|style|coords|shape)$/.test(n)) { 5481 v = e.getAttribute("data-mce-" + n); 5482 5483 if (v) 5484 return v; 5485 } 5486 5487 if (isIE && t.props[n]) { 5488 v = e[t.props[n]]; 5489 v = v && v.nodeValue ? v.nodeValue : v; 5490 } 5491 5492 if (!v) 5493 v = e.getAttribute(n, 2); 5494 5495 // Check boolean attribs 5496 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { 5497 if (e[t.props[n]] === true && v === '') 5498 return n; 5499 5500 return v ? n : ''; 5501 } 5502 5503 // Inner input elements will override attributes on form elements 5504 if (e.nodeName === "FORM" && e.getAttributeNode(n)) 5505 return e.getAttributeNode(n).nodeValue; 5506 5507 if (n === 'style') { 5508 v = v || e.style.cssText; 5509 5510 if (v) { 5511 v = t.serializeStyle(t.parseStyle(v), e.nodeName); 5512 5513 if (t.settings.keep_values && !t._isRes(v)) 5514 e.setAttribute('data-mce-style', v); 5515 } 5516 } 5517 5518 // Remove Apple and WebKit stuff 5519 if (isWebKit && n === "class" && v) 5520 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); 5521 5522 // Handle IE issues 5523 if (isIE) { 5524 switch (n) { 5525 case 'rowspan': 5526 case 'colspan': 5527 // IE returns 1 as default value 5528 if (v === 1) 5529 v = ''; 5530 5531 break; 5532 5533 case 'size': 5534 // IE returns +0 as default value for size 5535 if (v === '+0' || v === 20 || v === 0) 5536 v = ''; 5537 5538 break; 5539 5540 case 'width': 5541 case 'height': 5542 case 'vspace': 5543 case 'checked': 5544 case 'disabled': 5545 case 'readonly': 5546 if (v === 0) 5547 v = ''; 5548 5549 break; 5550 5551 case 'hspace': 5552 // IE returns -1 as default value 5553 if (v === -1) 5554 v = ''; 5555 5556 break; 5557 5558 case 'maxlength': 5559 case 'tabindex': 5560 // IE returns default value 5561 if (v === 32768 || v === 2147483647 || v === '32768') 5562 v = ''; 5563 5564 break; 5565 5566 case 'multiple': 5567 case 'compact': 5568 case 'noshade': 5569 case 'nowrap': 5570 if (v === 65535) 5571 return n; 5572 5573 return dv; 5574 5575 case 'shape': 5576 v = v.toLowerCase(); 5577 break; 5578 5579 default: 5580 // IE has odd anonymous function for event attributes 5581 if (n.indexOf('on') === 0 && v) 5582 v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v); 5583 } 5584 } 5585 5586 return (v !== undef && v !== null && v !== '') ? '' + v : dv; 5587 }, 5588 5589 getPos : function(n, ro) { 5590 var t = this, x = 0, y = 0, e, d = t.doc, r; 5591 5592 n = t.get(n); 5593 ro = ro || d.body; 5594 5595 if (n) { 5596 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes 5597 if (n.getBoundingClientRect) { 5598 n = n.getBoundingClientRect(); 5599 e = t.boxModel ? d.documentElement : d.body; 5600 5601 // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit 5602 // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position 5603 x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop; 5604 y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft; 5605 5606 return {x : x, y : y}; 5607 } 5608 5609 r = n; 5610 while (r && r != ro && r.nodeType) { 5611 x += r.offsetLeft || 0; 5612 y += r.offsetTop || 0; 5613 r = r.offsetParent; 5614 } 5615 5616 r = n.parentNode; 5617 while (r && r != ro && r.nodeType) { 5618 x -= r.scrollLeft || 0; 5619 y -= r.scrollTop || 0; 5620 r = r.parentNode; 5621 } 5622 } 5623 5624 return {x : x, y : y}; 5625 }, 5626 5627 parseStyle : function(st) { 5628 return this.styles.parse(st); 5629 }, 5630 5631 serializeStyle : function(o, name) { 5632 return this.styles.serialize(o, name); 5633 }, 5634 5635 addStyle: function(cssText) { 5636 var doc = this.doc, head; 5637 5638 // Create style element if needed 5639 styleElm = doc.getElementById('mceDefaultStyles'); 5640 if (!styleElm) { 5641 styleElm = doc.createElement('style'), 5642 styleElm.id = 'mceDefaultStyles'; 5643 styleElm.type = 'text/css'; 5644 5645 head = doc.getElementsByTagName('head')[0] 5646 if (head.firstChild) { 5647 head.insertBefore(styleElm, head.firstChild); 5648 } else { 5649 head.appendChild(styleElm); 5650 } 5651 } 5652 5653 // Append style data to old or new style element 5654 if (styleElm.styleSheet) { 5655 styleElm.styleSheet.cssText += cssText; 5656 } else { 5657 styleElm.appendChild(doc.createTextNode(cssText)); 5658 } 5659 }, 5660 5661 loadCSS : function(u) { 5662 var t = this, d = t.doc, head; 5663 5664 if (!u) 5665 u = ''; 5666 5667 head = d.getElementsByTagName('head')[0]; 5668 5669 each(u.split(','), function(u) { 5670 var link; 5671 5672 if (t.files[u]) 5673 return; 5674 5675 t.files[u] = true; 5676 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); 5677 5678 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug 5679 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading 5680 // It's ugly but it seems to work fine. 5681 if (isIE && d.documentMode && d.recalc) { 5682 link.onload = function() { 5683 if (d.recalc) 5684 d.recalc(); 5685 5686 link.onload = null; 5687 }; 5688 } 5689 5690 head.appendChild(link); 5691 }); 5692 }, 5693 5694 addClass : function(e, c) { 5695 return this.run(e, function(e) { 5696 var o; 5697 5698 if (!c) 5699 return 0; 5700 5701 if (this.hasClass(e, c)) 5702 return e.className; 5703 5704 o = this.removeClass(e, c); 5705 5706 return e.className = (o != '' ? (o + ' ') : '') + c; 5707 }); 5708 }, 5709 5710 removeClass : function(e, c) { 5711 var t = this, re; 5712 5713 return t.run(e, function(e) { 5714 var v; 5715 5716 if (t.hasClass(e, c)) { 5717 if (!re) 5718 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); 5719 5720 v = e.className.replace(re, ' '); 5721 v = tinymce.trim(v != ' ' ? v : ''); 5722 5723 e.className = v; 5724 5725 // Empty class attr 5726 if (!v) { 5727 e.removeAttribute('class'); 5728 e.removeAttribute('className'); 5729 } 5730 5731 return v; 5732 } 5733 5734 return e.className; 5735 }); 5736 }, 5737 5738 hasClass : function(n, c) { 5739 n = this.get(n); 5740 5741 if (!n || !c) 5742 return false; 5743 5744 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; 5745 }, 5746 5747 show : function(e) { 5748 return this.setStyle(e, 'display', 'block'); 5749 }, 5750 5751 hide : function(e) { 5752 return this.setStyle(e, 'display', 'none'); 5753 }, 5754 5755 isHidden : function(e) { 5756 e = this.get(e); 5757 5758 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; 5759 }, 5760 5761 uniqueId : function(p) { 5762 return (!p ? 'mce_' : p) + (this.counter++); 5763 }, 5764 5765 setHTML : function(element, html) { 5766 var self = this; 5767 5768 return self.run(element, function(element) { 5769 if (isIE) { 5770 // Remove all child nodes, IE keeps empty text nodes in DOM 5771 while (element.firstChild) 5772 element.removeChild(element.firstChild); 5773 5774 try { 5775 // IE will remove comments from the beginning 5776 // unless you padd the contents with something 5777 element.innerHTML = '<br />' + html; 5778 element.removeChild(element.firstChild); 5779 } catch (ex) { 5780 // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p 5781 // This seems to fix this problem 5782 5783 // Create new div with HTML contents and a BR infront to keep comments 5784 var newElement = self.create('div'); 5785 newElement.innerHTML = '<br />' + html; 5786 5787 // Add all children from div to target 5788 each (tinymce.grep(newElement.childNodes), function(node, i) { 5789 // Skip br element 5790 if (i && element.canHaveHTML) 5791 element.appendChild(node); 5792 }); 5793 } 5794 } else 5795 element.innerHTML = html; 5796 5797 return html; 5798 }); 5799 }, 5800 5801 getOuterHTML : function(elm) { 5802 var doc, self = this; 5803 5804 elm = self.get(elm); 5805 5806 if (!elm) 5807 return null; 5808 5809 if (elm.nodeType === 1 && self.hasOuterHTML) 5810 return elm.outerHTML; 5811 5812 doc = (elm.ownerDocument || self.doc).createElement("body"); 5813 doc.appendChild(elm.cloneNode(true)); 5814 5815 return doc.innerHTML; 5816 }, 5817 5818 setOuterHTML : function(e, h, d) { 5819 var t = this; 5820 5821 function setHTML(e, h, d) { 5822 var n, tp; 5823 5824 tp = d.createElement("body"); 5825 tp.innerHTML = h; 5826 5827 n = tp.lastChild; 5828 while (n) { 5829 t.insertAfter(n.cloneNode(true), e); 5830 n = n.previousSibling; 5831 } 5832 5833 t.remove(e); 5834 }; 5835 5836 return this.run(e, function(e) { 5837 e = t.get(e); 5838 5839 // Only set HTML on elements 5840 if (e.nodeType == 1) { 5841 d = d || e.ownerDocument || t.doc; 5842 5843 if (isIE) { 5844 try { 5845 // Try outerHTML for IE it sometimes produces an unknown runtime error 5846 if (isIE && e.nodeType == 1) 5847 e.outerHTML = h; 5848 else 5849 setHTML(e, h, d); 5850 } catch (ex) { 5851 // Fix for unknown runtime error 5852 setHTML(e, h, d); 5853 } 5854 } else 5855 setHTML(e, h, d); 5856 } 5857 }); 5858 }, 5859 5860 decode : Entities.decode, 5861 5862 encode : Entities.encodeAllRaw, 5863 5864 insertAfter : function(node, reference_node) { 5865 reference_node = this.get(reference_node); 5866 5867 return this.run(node, function(node) { 5868 var parent, nextSibling; 5869 5870 parent = reference_node.parentNode; 5871 nextSibling = reference_node.nextSibling; 5872 5873 if (nextSibling) 5874 parent.insertBefore(node, nextSibling); 5875 else 5876 parent.appendChild(node); 5877 5878 return node; 5879 }); 5880 }, 5881 5882 replace : function(n, o, k) { 5883 var t = this; 5884 5885 if (is(o, 'array')) 5886 n = n.cloneNode(true); 5887 5888 return t.run(o, function(o) { 5889 if (k) { 5890 each(tinymce.grep(o.childNodes), function(c) { 5891 n.appendChild(c); 5892 }); 5893 } 5894 5895 return o.parentNode.replaceChild(n, o); 5896 }); 5897 }, 5898 5899 rename : function(elm, name) { 5900 var t = this, newElm; 5901 5902 if (elm.nodeName != name.toUpperCase()) { 5903 // Rename block element 5904 newElm = t.create(name); 5905 5906 // Copy attribs to new block 5907 each(t.getAttribs(elm), function(attr_node) { 5908 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); 5909 }); 5910 5911 // Replace block 5912 t.replace(newElm, elm, 1); 5913 } 5914 5915 return newElm || elm; 5916 }, 5917 5918 findCommonAncestor : function(a, b) { 5919 var ps = a, pe; 5920 5921 while (ps) { 5922 pe = b; 5923 5924 while (pe && ps != pe) 5925 pe = pe.parentNode; 5926 5927 if (ps == pe) 5928 break; 5929 5930 ps = ps.parentNode; 5931 } 5932 5933 if (!ps && a.ownerDocument) 5934 return a.ownerDocument.documentElement; 5935 5936 return ps; 5937 }, 5938 5939 toHex : function(s) { 5940 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); 5941 5942 function hex(s) { 5943 s = parseInt(s, 10).toString(16); 5944 5945 return s.length > 1 ? s : '0' + s; // 0 -> 00 5946 }; 5947 5948 if (c) { 5949 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); 5950 5951 return s; 5952 } 5953 5954 return s; 5955 }, 5956 5957 getClasses : function() { 5958 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; 5959 5960 if (t.classes) 5961 return t.classes; 5962 5963 function addClasses(s) { 5964 // IE style imports 5965 each(s.imports, function(r) { 5966 addClasses(r); 5967 }); 5968 5969 each(s.cssRules || s.rules, function(r) { 5970 // Real type or fake it on IE 5971 switch (r.type || 1) { 5972 // Rule 5973 case 1: 5974 if (r.selectorText) { 5975 each(r.selectorText.split(','), function(v) { 5976 v = v.replace(/^\s*|\s*$|^\s\./g, ""); 5977 5978 // Is internal or it doesn't contain a class 5979 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) 5980 return; 5981 5982 // Remove everything but class name 5983 ov = v; 5984 v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); 5985 5986 // Filter classes 5987 if (f && !(v = f(v, ov))) 5988 return; 5989 5990 if (!lo[v]) { 5991 cl.push({'class' : v}); 5992 lo[v] = 1; 5993 } 5994 }); 5995 } 5996 break; 5997 5998 // Import 5999 case 3: 6000 addClasses(r.styleSheet); 6001 break; 6002 } 6003 }); 6004 }; 6005 6006 try { 6007 each(t.doc.styleSheets, addClasses); 6008 } catch (ex) { 6009 // Ignore 6010 } 6011 6012 if (cl.length > 0) 6013 t.classes = cl; 6014 6015 return cl; 6016 }, 6017 6018 run : function(e, f, s) { 6019 var t = this, o; 6020 6021 if (t.doc && typeof(e) === 'string') 6022 e = t.get(e); 6023 6024 if (!e) 6025 return false; 6026 6027 s = s || this; 6028 if (!e.nodeType && (e.length || e.length === 0)) { 6029 o = []; 6030 6031 each(e, function(e, i) { 6032 if (e) { 6033 if (typeof(e) == 'string') 6034 e = t.doc.getElementById(e); 6035 6036 o.push(f.call(s, e, i)); 6037 } 6038 }); 6039 6040 return o; 6041 } 6042 6043 return f.call(s, e); 6044 }, 6045 6046 getAttribs : function(n) { 6047 var o; 6048 6049 n = this.get(n); 6050 6051 if (!n) 6052 return []; 6053 6054 if (isIE) { 6055 o = []; 6056 6057 // Object will throw exception in IE 6058 if (n.nodeName == 'OBJECT') 6059 return n.attributes; 6060 6061 // IE doesn't keep the selected attribute if you clone option elements 6062 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) 6063 o.push({specified : 1, nodeName : 'selected'}); 6064 6065 // It's crazy that this is faster in IE but it's because it returns all attributes all the time 6066 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { 6067 o.push({specified : 1, nodeName : a}); 6068 }); 6069 6070 return o; 6071 } 6072 6073 return n.attributes; 6074 }, 6075 6076 isEmpty : function(node, elements) { 6077 var self = this, i, attributes, type, walker, name, brCount = 0; 6078 6079 node = node.firstChild; 6080 if (node) { 6081 walker = new tinymce.dom.TreeWalker(node, node.parentNode); 6082 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null; 6083 6084 do { 6085 type = node.nodeType; 6086 6087 if (type === 1) { 6088 // Ignore bogus elements 6089 if (node.getAttribute('data-mce-bogus')) 6090 continue; 6091 6092 // Keep empty elements like <img /> 6093 name = node.nodeName.toLowerCase(); 6094 if (elements && elements[name]) { 6095 // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p> 6096 if (name === 'br') { 6097 brCount++; 6098 continue; 6099 } 6100 6101 return false; 6102 } 6103 6104 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a> 6105 attributes = self.getAttribs(node); 6106 i = node.attributes.length; 6107 while (i--) { 6108 name = node.attributes[i].nodeName; 6109 if (name === "name" || name === 'data-mce-bookmark') 6110 return false; 6111 } 6112 } 6113 6114 // Keep comment nodes 6115 if (type == 8) 6116 return false; 6117 6118 // Keep non whitespace text nodes 6119 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) 6120 return false; 6121 } while (node = walker.next()); 6122 } 6123 6124 return brCount <= 1; 6125 }, 6126 6127 destroy : function(s) { 6128 var t = this; 6129 6130 t.win = t.doc = t.root = t.events = t.frag = null; 6131 6132 // Manual destroy then remove unload handler 6133 if (!s) 6134 tinymce.removeUnload(t.destroy); 6135 }, 6136 6137 createRng : function() { 6138 var d = this.doc; 6139 6140 return d.createRange ? d.createRange() : new tinymce.dom.Range(this); 6141 }, 6142 6143 nodeIndex : function(node, normalized) { 6144 var idx = 0, lastNodeType, lastNode, nodeType; 6145 6146 if (node) { 6147 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { 6148 nodeType = node.nodeType; 6149 6150 // Normalize text nodes 6151 if (normalized && nodeType == 3) { 6152 if (nodeType == lastNodeType || !node.nodeValue.length) 6153 continue; 6154 } 6155 idx++; 6156 lastNodeType = nodeType; 6157 } 6158 } 6159 6160 return idx; 6161 }, 6162 6163 split : function(pe, e, re) { 6164 var t = this, r = t.createRng(), bef, aft, pa; 6165 6166 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense 6167 // but we don't want that in our code since it serves no purpose for the end user 6168 // For example if this is chopped: 6169 // <p>text 1<span><b>CHOP</b></span>text 2</p> 6170 // would produce: 6171 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p> 6172 // this function will then trim of empty edges and produce: 6173 // <p>text 1</p><b>CHOP</b><p>text 2</p> 6174 function trim(node) { 6175 var i, children = node.childNodes, type = node.nodeType; 6176 6177 function surroundedBySpans(node) { 6178 var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN'; 6179 var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN'; 6180 return previousIsSpan && nextIsSpan; 6181 } 6182 6183 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') 6184 return; 6185 6186 for (i = children.length - 1; i >= 0; i--) 6187 trim(children[i]); 6188 6189 if (type != 9) { 6190 // Keep non whitespace text nodes 6191 if (type == 3 && node.nodeValue.length > 0) { 6192 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>" 6193 // Also keep text nodes with only spaces if surrounded by spans. 6194 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b 6195 var trimmedLength = tinymce.trim(node.nodeValue).length; 6196 if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) 6197 return; 6198 } else if (type == 1) { 6199 // If the only child is a bookmark then move it up 6200 children = node.childNodes; 6201 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark') 6202 node.parentNode.insertBefore(children[0], node); 6203 6204 // Keep non empty elements or img, hr etc 6205 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) 6206 return; 6207 } 6208 6209 t.remove(node); 6210 } 6211 6212 return node; 6213 }; 6214 6215 if (pe && e) { 6216 // Get before chunk 6217 r.setStart(pe.parentNode, t.nodeIndex(pe)); 6218 r.setEnd(e.parentNode, t.nodeIndex(e)); 6219 bef = r.extractContents(); 6220 6221 // Get after chunk 6222 r = t.createRng(); 6223 r.setStart(e.parentNode, t.nodeIndex(e) + 1); 6224 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); 6225 aft = r.extractContents(); 6226 6227 // Insert before chunk 6228 pa = pe.parentNode; 6229 pa.insertBefore(trim(bef), pe); 6230 6231 // Insert middle chunk 6232 if (re) 6233 pa.replaceChild(re, e); 6234 else 6235 pa.insertBefore(e, pe); 6236 6237 // Insert after chunk 6238 pa.insertBefore(trim(aft), pe); 6239 t.remove(pe); 6240 6241 return re || e; 6242 } 6243 }, 6244 6245 bind : function(target, name, func, scope) { 6246 return this.events.add(target, name, func, scope || this); 6247 }, 6248 6249 unbind : function(target, name, func) { 6250 return this.events.remove(target, name, func); 6251 }, 6252 6253 fire : function(target, name, evt) { 6254 return this.events.fire(target, name, evt); 6255 }, 6256 6257 // Returns the content editable state of a node 6258 getContentEditable: function(node) { 6259 var contentEditable; 6260 6261 // Check type 6262 if (node.nodeType != 1) { 6263 return null; 6264 } 6265 6266 // Check for fake content editable 6267 contentEditable = node.getAttribute("data-mce-contenteditable"); 6268 if (contentEditable && contentEditable !== "inherit") { 6269 return contentEditable; 6270 } 6271 6272 // Check for real content editable 6273 return node.contentEditable !== "inherit" ? node.contentEditable : null; 6274 }, 6275 6276 6277 _findSib : function(node, selector, name) { 6278 var t = this, f = selector; 6279 6280 if (node) { 6281 // If expression make a function of it using is 6282 if (is(f, 'string')) { 6283 f = function(node) { 6284 return t.is(node, selector); 6285 }; 6286 } 6287 6288 // Loop all siblings 6289 for (node = node[name]; node; node = node[name]) { 6290 if (f(node)) 6291 return node; 6292 } 6293 } 6294 6295 return null; 6296 }, 6297 6298 _isRes : function(c) { 6299 // Is live resizble element 6300 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); 6301 } 6302 6303 /* 6304 walk : function(n, f, s) { 6305 var d = this.doc, w; 6306 6307 if (d.createTreeWalker) { 6308 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); 6309 6310 while ((n = w.nextNode()) != null) 6311 f.call(s || this, n); 6312 } else 6313 tinymce.walk(n, f, 'childNodes', s); 6314 } 6315 */ 6316 6317 /* 6318 toRGB : function(s) { 6319 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); 6320 6321 if (c) { 6322 // #FFF -> #FFFFFF 6323 if (!is(c[3])) 6324 c[3] = c[2] = c[1]; 6325 6326 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; 6327 } 6328 6329 return s; 6330 } 6331 */ 6332 }); 6333 6334 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); 6335 })(tinymce); 6336 6337 (function(ns) { 6338 // Range constructor 6339 function Range(dom) { 6340 var t = this, 6341 doc = dom.doc, 6342 EXTRACT = 0, 6343 CLONE = 1, 6344 DELETE = 2, 6345 TRUE = true, 6346 FALSE = false, 6347 START_OFFSET = 'startOffset', 6348 START_CONTAINER = 'startContainer', 6349 END_CONTAINER = 'endContainer', 6350 END_OFFSET = 'endOffset', 6351 extend = tinymce.extend, 6352 nodeIndex = dom.nodeIndex; 6353 6354 extend(t, { 6355 // Inital states 6356 startContainer : doc, 6357 startOffset : 0, 6358 endContainer : doc, 6359 endOffset : 0, 6360 collapsed : TRUE, 6361 commonAncestorContainer : doc, 6362 6363 // Range constants 6364 START_TO_START : 0, 6365 START_TO_END : 1, 6366 END_TO_END : 2, 6367 END_TO_START : 3, 6368 6369 // Public methods 6370 setStart : setStart, 6371 setEnd : setEnd, 6372 setStartBefore : setStartBefore, 6373 setStartAfter : setStartAfter, 6374 setEndBefore : setEndBefore, 6375 setEndAfter : setEndAfter, 6376 collapse : collapse, 6377 selectNode : selectNode, 6378 selectNodeContents : selectNodeContents, 6379 compareBoundaryPoints : compareBoundaryPoints, 6380 deleteContents : deleteContents, 6381 extractContents : extractContents, 6382 cloneContents : cloneContents, 6383 insertNode : insertNode, 6384 surroundContents : surroundContents, 6385 cloneRange : cloneRange, 6386 toStringIE : toStringIE 6387 }); 6388 6389 function createDocumentFragment() { 6390 return doc.createDocumentFragment(); 6391 }; 6392 6393 function setStart(n, o) { 6394 _setEndPoint(TRUE, n, o); 6395 }; 6396 6397 function setEnd(n, o) { 6398 _setEndPoint(FALSE, n, o); 6399 }; 6400 6401 function setStartBefore(n) { 6402 setStart(n.parentNode, nodeIndex(n)); 6403 }; 6404 6405 function setStartAfter(n) { 6406 setStart(n.parentNode, nodeIndex(n) + 1); 6407 }; 6408 6409 function setEndBefore(n) { 6410 setEnd(n.parentNode, nodeIndex(n)); 6411 }; 6412 6413 function setEndAfter(n) { 6414 setEnd(n.parentNode, nodeIndex(n) + 1); 6415 }; 6416 6417 function collapse(ts) { 6418 if (ts) { 6419 t[END_CONTAINER] = t[START_CONTAINER]; 6420 t[END_OFFSET] = t[START_OFFSET]; 6421 } else { 6422 t[START_CONTAINER] = t[END_CONTAINER]; 6423 t[START_OFFSET] = t[END_OFFSET]; 6424 } 6425 6426 t.collapsed = TRUE; 6427 }; 6428 6429 function selectNode(n) { 6430 setStartBefore(n); 6431 setEndAfter(n); 6432 }; 6433 6434 function selectNodeContents(n) { 6435 setStart(n, 0); 6436 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); 6437 }; 6438 6439 function compareBoundaryPoints(h, r) { 6440 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET], 6441 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; 6442 6443 // Check START_TO_START 6444 if (h === 0) 6445 return _compareBoundaryPoints(sc, so, rsc, rso); 6446 6447 // Check START_TO_END 6448 if (h === 1) 6449 return _compareBoundaryPoints(ec, eo, rsc, rso); 6450 6451 // Check END_TO_END 6452 if (h === 2) 6453 return _compareBoundaryPoints(ec, eo, rec, reo); 6454 6455 // Check END_TO_START 6456 if (h === 3) 6457 return _compareBoundaryPoints(sc, so, rec, reo); 6458 }; 6459 6460 function deleteContents() { 6461 _traverse(DELETE); 6462 }; 6463 6464 function extractContents() { 6465 return _traverse(EXTRACT); 6466 }; 6467 6468 function cloneContents() { 6469 return _traverse(CLONE); 6470 }; 6471 6472 function insertNode(n) { 6473 var startContainer = this[START_CONTAINER], 6474 startOffset = this[START_OFFSET], nn, o; 6475 6476 // Node is TEXT_NODE or CDATA 6477 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { 6478 if (!startOffset) { 6479 // At the start of text 6480 startContainer.parentNode.insertBefore(n, startContainer); 6481 } else if (startOffset >= startContainer.nodeValue.length) { 6482 // At the end of text 6483 dom.insertAfter(n, startContainer); 6484 } else { 6485 // Middle, need to split 6486 nn = startContainer.splitText(startOffset); 6487 startContainer.parentNode.insertBefore(n, nn); 6488 } 6489 } else { 6490 // Insert element node 6491 if (startContainer.childNodes.length > 0) 6492 o = startContainer.childNodes[startOffset]; 6493 6494 if (o) 6495 startContainer.insertBefore(n, o); 6496 else 6497 startContainer.appendChild(n); 6498 } 6499 }; 6500 6501 function surroundContents(n) { 6502 var f = t.extractContents(); 6503 6504 t.insertNode(n); 6505 n.appendChild(f); 6506 t.selectNode(n); 6507 }; 6508 6509 function cloneRange() { 6510 return extend(new Range(dom), { 6511 startContainer : t[START_CONTAINER], 6512 startOffset : t[START_OFFSET], 6513 endContainer : t[END_CONTAINER], 6514 endOffset : t[END_OFFSET], 6515 collapsed : t.collapsed, 6516 commonAncestorContainer : t.commonAncestorContainer 6517 }); 6518 }; 6519 6520 // Private methods 6521 6522 function _getSelectedNode(container, offset) { 6523 var child; 6524 6525 if (container.nodeType == 3 /* TEXT_NODE */) 6526 return container; 6527 6528 if (offset < 0) 6529 return container; 6530 6531 child = container.firstChild; 6532 while (child && offset > 0) { 6533 --offset; 6534 child = child.nextSibling; 6535 } 6536 6537 if (child) 6538 return child; 6539 6540 return container; 6541 }; 6542 6543 function _isCollapsed() { 6544 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); 6545 }; 6546 6547 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { 6548 var c, offsetC, n, cmnRoot, childA, childB; 6549 6550 // In the first case the boundary-points have the same container. A is before B 6551 // if its offset is less than the offset of B, A is equal to B if its offset is 6552 // equal to the offset of B, and A is after B if its offset is greater than the 6553 // offset of B. 6554 if (containerA == containerB) { 6555 if (offsetA == offsetB) 6556 return 0; // equal 6557 6558 if (offsetA < offsetB) 6559 return -1; // before 6560 6561 return 1; // after 6562 } 6563 6564 // In the second case a child node C of the container of A is an ancestor 6565 // container of B. In this case, A is before B if the offset of A is less than or 6566 // equal to the index of the child node C and A is after B otherwise. 6567 c = containerB; 6568 while (c && c.parentNode != containerA) 6569 c = c.parentNode; 6570 6571 if (c) { 6572 offsetC = 0; 6573 n = containerA.firstChild; 6574 6575 while (n != c && offsetC < offsetA) { 6576 offsetC++; 6577 n = n.nextSibling; 6578 } 6579 6580 if (offsetA <= offsetC) 6581 return -1; // before 6582 6583 return 1; // after 6584 } 6585 6586 // In the third case a child node C of the container of B is an ancestor container 6587 // of A. In this case, A is before B if the index of the child node C is less than 6588 // the offset of B and A is after B otherwise. 6589 c = containerA; 6590 while (c && c.parentNode != containerB) { 6591 c = c.parentNode; 6592 } 6593 6594 if (c) { 6595 offsetC = 0; 6596 n = containerB.firstChild; 6597 6598 while (n != c && offsetC < offsetB) { 6599 offsetC++; 6600 n = n.nextSibling; 6601 } 6602 6603 if (offsetC < offsetB) 6604 return -1; // before 6605 6606 return 1; // after 6607 } 6608 6609 // In the fourth case, none of three other cases hold: the containers of A and B 6610 // are siblings or descendants of sibling nodes. In this case, A is before B if 6611 // the container of A is before the container of B in a pre-order traversal of the 6612 // Ranges' context tree and A is after B otherwise. 6613 cmnRoot = dom.findCommonAncestor(containerA, containerB); 6614 childA = containerA; 6615 6616 while (childA && childA.parentNode != cmnRoot) 6617 childA = childA.parentNode; 6618 6619 if (!childA) 6620 childA = cmnRoot; 6621 6622 childB = containerB; 6623 while (childB && childB.parentNode != cmnRoot) 6624 childB = childB.parentNode; 6625 6626 if (!childB) 6627 childB = cmnRoot; 6628 6629 if (childA == childB) 6630 return 0; // equal 6631 6632 n = cmnRoot.firstChild; 6633 while (n) { 6634 if (n == childA) 6635 return -1; // before 6636 6637 if (n == childB) 6638 return 1; // after 6639 6640 n = n.nextSibling; 6641 } 6642 }; 6643 6644 function _setEndPoint(st, n, o) { 6645 var ec, sc; 6646 6647 if (st) { 6648 t[START_CONTAINER] = n; 6649 t[START_OFFSET] = o; 6650 } else { 6651 t[END_CONTAINER] = n; 6652 t[END_OFFSET] = o; 6653 } 6654 6655 // If one boundary-point of a Range is set to have a root container 6656 // other than the current one for the Range, the Range is collapsed to 6657 // the new position. This enforces the restriction that both boundary- 6658 // points of a Range must have the same root container. 6659 ec = t[END_CONTAINER]; 6660 while (ec.parentNode) 6661 ec = ec.parentNode; 6662 6663 sc = t[START_CONTAINER]; 6664 while (sc.parentNode) 6665 sc = sc.parentNode; 6666 6667 if (sc == ec) { 6668 // The start position of a Range is guaranteed to never be after the 6669 // end position. To enforce this restriction, if the start is set to 6670 // be at a position after the end, the Range is collapsed to that 6671 // position. 6672 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) 6673 t.collapse(st); 6674 } else 6675 t.collapse(st); 6676 6677 t.collapsed = _isCollapsed(); 6678 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); 6679 }; 6680 6681 function _traverse(how) { 6682 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; 6683 6684 if (t[START_CONTAINER] == t[END_CONTAINER]) 6685 return _traverseSameContainer(how); 6686 6687 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 6688 if (p == t[START_CONTAINER]) 6689 return _traverseCommonStartContainer(c, how); 6690 6691 ++endContainerDepth; 6692 } 6693 6694 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 6695 if (p == t[END_CONTAINER]) 6696 return _traverseCommonEndContainer(c, how); 6697 6698 ++startContainerDepth; 6699 } 6700 6701 depthDiff = startContainerDepth - endContainerDepth; 6702 6703 startNode = t[START_CONTAINER]; 6704 while (depthDiff > 0) { 6705 startNode = startNode.parentNode; 6706 depthDiff--; 6707 } 6708 6709 endNode = t[END_CONTAINER]; 6710 while (depthDiff < 0) { 6711 endNode = endNode.parentNode; 6712 depthDiff++; 6713 } 6714 6715 // ascend the ancestor hierarchy until we have a common parent. 6716 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { 6717 startNode = sp; 6718 endNode = ep; 6719 } 6720 6721 return _traverseCommonAncestors(startNode, endNode, how); 6722 }; 6723 6724 function _traverseSameContainer(how) { 6725 var frag, s, sub, n, cnt, sibling, xferNode, start, len; 6726 6727 if (how != DELETE) 6728 frag = createDocumentFragment(); 6729 6730 // If selection is empty, just return the fragment 6731 if (t[START_OFFSET] == t[END_OFFSET]) 6732 return frag; 6733 6734 // Text node needs special case handling 6735 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { 6736 // get the substring 6737 s = t[START_CONTAINER].nodeValue; 6738 sub = s.substring(t[START_OFFSET], t[END_OFFSET]); 6739 6740 // set the original text node to its new value 6741 if (how != CLONE) { 6742 n = t[START_CONTAINER]; 6743 start = t[START_OFFSET]; 6744 len = t[END_OFFSET] - t[START_OFFSET]; 6745 6746 if (start === 0 && len >= n.nodeValue.length - 1) { 6747 n.parentNode.removeChild(n); 6748 } else { 6749 n.deleteData(start, len); 6750 } 6751 6752 // Nothing is partially selected, so collapse to start point 6753 t.collapse(TRUE); 6754 } 6755 6756 if (how == DELETE) 6757 return; 6758 6759 if (sub.length > 0) { 6760 frag.appendChild(doc.createTextNode(sub)); 6761 } 6762 6763 return frag; 6764 } 6765 6766 // Copy nodes between the start/end offsets. 6767 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); 6768 cnt = t[END_OFFSET] - t[START_OFFSET]; 6769 6770 while (n && cnt > 0) { 6771 sibling = n.nextSibling; 6772 xferNode = _traverseFullySelected(n, how); 6773 6774 if (frag) 6775 frag.appendChild( xferNode ); 6776 6777 --cnt; 6778 n = sibling; 6779 } 6780 6781 // Nothing is partially selected, so collapse to start point 6782 if (how != CLONE) 6783 t.collapse(TRUE); 6784 6785 return frag; 6786 }; 6787 6788 function _traverseCommonStartContainer(endAncestor, how) { 6789 var frag, n, endIdx, cnt, sibling, xferNode; 6790 6791 if (how != DELETE) 6792 frag = createDocumentFragment(); 6793 6794 n = _traverseRightBoundary(endAncestor, how); 6795 6796 if (frag) 6797 frag.appendChild(n); 6798 6799 endIdx = nodeIndex(endAncestor); 6800 cnt = endIdx - t[START_OFFSET]; 6801 6802 if (cnt <= 0) { 6803 // Collapse to just before the endAncestor, which 6804 // is partially selected. 6805 if (how != CLONE) { 6806 t.setEndBefore(endAncestor); 6807 t.collapse(FALSE); 6808 } 6809 6810 return frag; 6811 } 6812 6813 n = endAncestor.previousSibling; 6814 while (cnt > 0) { 6815 sibling = n.previousSibling; 6816 xferNode = _traverseFullySelected(n, how); 6817 6818 if (frag) 6819 frag.insertBefore(xferNode, frag.firstChild); 6820 6821 --cnt; 6822 n = sibling; 6823 } 6824 6825 // Collapse to just before the endAncestor, which 6826 // is partially selected. 6827 if (how != CLONE) { 6828 t.setEndBefore(endAncestor); 6829 t.collapse(FALSE); 6830 } 6831 6832 return frag; 6833 }; 6834 6835 function _traverseCommonEndContainer(startAncestor, how) { 6836 var frag, startIdx, n, cnt, sibling, xferNode; 6837 6838 if (how != DELETE) 6839 frag = createDocumentFragment(); 6840 6841 n = _traverseLeftBoundary(startAncestor, how); 6842 if (frag) 6843 frag.appendChild(n); 6844 6845 startIdx = nodeIndex(startAncestor); 6846 ++startIdx; // Because we already traversed it 6847 6848 cnt = t[END_OFFSET] - startIdx; 6849 n = startAncestor.nextSibling; 6850 while (n && cnt > 0) { 6851 sibling = n.nextSibling; 6852 xferNode = _traverseFullySelected(n, how); 6853 6854 if (frag) 6855 frag.appendChild(xferNode); 6856 6857 --cnt; 6858 n = sibling; 6859 } 6860 6861 if (how != CLONE) { 6862 t.setStartAfter(startAncestor); 6863 t.collapse(TRUE); 6864 } 6865 6866 return frag; 6867 }; 6868 6869 function _traverseCommonAncestors(startAncestor, endAncestor, how) { 6870 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; 6871 6872 if (how != DELETE) 6873 frag = createDocumentFragment(); 6874 6875 n = _traverseLeftBoundary(startAncestor, how); 6876 if (frag) 6877 frag.appendChild(n); 6878 6879 commonParent = startAncestor.parentNode; 6880 startOffset = nodeIndex(startAncestor); 6881 endOffset = nodeIndex(endAncestor); 6882 ++startOffset; 6883 6884 cnt = endOffset - startOffset; 6885 sibling = startAncestor.nextSibling; 6886 6887 while (cnt > 0) { 6888 nextSibling = sibling.nextSibling; 6889 n = _traverseFullySelected(sibling, how); 6890 6891 if (frag) 6892 frag.appendChild(n); 6893 6894 sibling = nextSibling; 6895 --cnt; 6896 } 6897 6898 n = _traverseRightBoundary(endAncestor, how); 6899 6900 if (frag) 6901 frag.appendChild(n); 6902 6903 if (how != CLONE) { 6904 t.setStartAfter(startAncestor); 6905 t.collapse(TRUE); 6906 } 6907 6908 return frag; 6909 }; 6910 6911 function _traverseRightBoundary(root, how) { 6912 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; 6913 6914 if (next == root) 6915 return _traverseNode(next, isFullySelected, FALSE, how); 6916 6917 parent = next.parentNode; 6918 clonedParent = _traverseNode(parent, FALSE, FALSE, how); 6919 6920 while (parent) { 6921 while (next) { 6922 prevSibling = next.previousSibling; 6923 clonedChild = _traverseNode(next, isFullySelected, FALSE, how); 6924 6925 if (how != DELETE) 6926 clonedParent.insertBefore(clonedChild, clonedParent.firstChild); 6927 6928 isFullySelected = TRUE; 6929 next = prevSibling; 6930 } 6931 6932 if (parent == root) 6933 return clonedParent; 6934 6935 next = parent.previousSibling; 6936 parent = parent.parentNode; 6937 6938 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); 6939 6940 if (how != DELETE) 6941 clonedGrandParent.appendChild(clonedParent); 6942 6943 clonedParent = clonedGrandParent; 6944 } 6945 }; 6946 6947 function _traverseLeftBoundary(root, how) { 6948 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; 6949 6950 if (next == root) 6951 return _traverseNode(next, isFullySelected, TRUE, how); 6952 6953 parent = next.parentNode; 6954 clonedParent = _traverseNode(parent, FALSE, TRUE, how); 6955 6956 while (parent) { 6957 while (next) { 6958 nextSibling = next.nextSibling; 6959 clonedChild = _traverseNode(next, isFullySelected, TRUE, how); 6960 6961 if (how != DELETE) 6962 clonedParent.appendChild(clonedChild); 6963 6964 isFullySelected = TRUE; 6965 next = nextSibling; 6966 } 6967 6968 if (parent == root) 6969 return clonedParent; 6970 6971 next = parent.nextSibling; 6972 parent = parent.parentNode; 6973 6974 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); 6975 6976 if (how != DELETE) 6977 clonedGrandParent.appendChild(clonedParent); 6978 6979 clonedParent = clonedGrandParent; 6980 } 6981 }; 6982 6983 function _traverseNode(n, isFullySelected, isLeft, how) { 6984 var txtValue, newNodeValue, oldNodeValue, offset, newNode; 6985 6986 if (isFullySelected) 6987 return _traverseFullySelected(n, how); 6988 6989 if (n.nodeType == 3 /* TEXT_NODE */) { 6990 txtValue = n.nodeValue; 6991 6992 if (isLeft) { 6993 offset = t[START_OFFSET]; 6994 newNodeValue = txtValue.substring(offset); 6995 oldNodeValue = txtValue.substring(0, offset); 6996 } else { 6997 offset = t[END_OFFSET]; 6998 newNodeValue = txtValue.substring(0, offset); 6999 oldNodeValue = txtValue.substring(offset); 7000 } 7001 7002 if (how != CLONE) 7003 n.nodeValue = oldNodeValue; 7004 7005 if (how == DELETE) 7006 return; 7007 7008 newNode = dom.clone(n, FALSE); 7009 newNode.nodeValue = newNodeValue; 7010 7011 return newNode; 7012 } 7013 7014 if (how == DELETE) 7015 return; 7016 7017 return dom.clone(n, FALSE); 7018 }; 7019 7020 function _traverseFullySelected(n, how) { 7021 if (how != DELETE) 7022 return how == CLONE ? dom.clone(n, TRUE) : n; 7023 7024 n.parentNode.removeChild(n); 7025 }; 7026 7027 function toStringIE() { 7028 return dom.create('body', null, cloneContents()).outerText; 7029 } 7030 7031 return t; 7032 }; 7033 7034 ns.Range = Range; 7035 7036 // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype 7037 Range.prototype.toString = function() { 7038 return this.toStringIE(); 7039 }; 7040 })(tinymce.dom); 7041 7042 (function() { 7043 function Selection(selection) { 7044 var self = this, dom = selection.dom, TRUE = true, FALSE = false; 7045 7046 function getPosition(rng, start) { 7047 var checkRng, startIndex = 0, endIndex, inside, 7048 children, child, offset, index, position = -1, parent; 7049 7050 // Setup test range, collapse it and get the parent 7051 checkRng = rng.duplicate(); 7052 checkRng.collapse(start); 7053 parent = checkRng.parentElement(); 7054 7055 // Check if the selection is within the right document 7056 if (parent.ownerDocument !== selection.dom.doc) 7057 return; 7058 7059 // IE will report non editable elements as it's parent so look for an editable one 7060 while (parent.contentEditable === "false") { 7061 parent = parent.parentNode; 7062 } 7063 7064 // If parent doesn't have any children then return that we are inside the element 7065 if (!parent.hasChildNodes()) { 7066 return {node : parent, inside : 1}; 7067 } 7068 7069 // Setup node list and endIndex 7070 children = parent.children; 7071 endIndex = children.length - 1; 7072 7073 // Perform a binary search for the position 7074 while (startIndex <= endIndex) { 7075 index = Math.floor((startIndex + endIndex) / 2); 7076 7077 // Move selection to node and compare the ranges 7078 child = children[index]; 7079 checkRng.moveToElementText(child); 7080 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); 7081 7082 // Before/after or an exact match 7083 if (position > 0) { 7084 endIndex = index - 1; 7085 } else if (position < 0) { 7086 startIndex = index + 1; 7087 } else { 7088 return {node : child}; 7089 } 7090 } 7091 7092 // Check if child position is before or we didn't find a position 7093 if (position < 0) { 7094 // No element child was found use the parent element and the offset inside that 7095 if (!child) { 7096 checkRng.moveToElementText(parent); 7097 checkRng.collapse(true); 7098 child = parent; 7099 inside = true; 7100 } else 7101 checkRng.collapse(false); 7102 7103 // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one 7104 // We need to walk char by char since rng.text or rng.htmlText will trim line endings 7105 offset = 0; 7106 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7107 if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) { 7108 break; 7109 } 7110 7111 offset++; 7112 } 7113 } else { 7114 // Child position is after the selection endpoint 7115 checkRng.collapse(true); 7116 7117 // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one 7118 offset = 0; 7119 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7120 if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) { 7121 break; 7122 } 7123 7124 offset++; 7125 } 7126 } 7127 7128 return {node : child, position : position, offset : offset, inside : inside}; 7129 }; 7130 7131 // Returns a W3C DOM compatible range object by using the IE Range API 7132 function getRange() { 7133 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail; 7134 7135 // If selection is outside the current document just return an empty range 7136 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); 7137 if (element.ownerDocument != dom.doc) 7138 return domRange; 7139 7140 collapsed = selection.isCollapsed(); 7141 7142 // Handle control selection 7143 if (ieRange.item) { 7144 domRange.setStart(element.parentNode, dom.nodeIndex(element)); 7145 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); 7146 7147 return domRange; 7148 } 7149 7150 function findEndPoint(start) { 7151 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; 7152 7153 container = endPoint.node; 7154 offset = endPoint.offset; 7155 7156 if (endPoint.inside && !container.hasChildNodes()) { 7157 domRange[start ? 'setStart' : 'setEnd'](container, 0); 7158 return; 7159 } 7160 7161 if (offset === undef) { 7162 domRange[start ? 'setStartBefore' : 'setEndAfter'](container); 7163 return; 7164 } 7165 7166 if (endPoint.position < 0) { 7167 sibling = endPoint.inside ? container.firstChild : container.nextSibling; 7168 7169 if (!sibling) { 7170 domRange[start ? 'setStartAfter' : 'setEndAfter'](container); 7171 return; 7172 } 7173 7174 if (!offset) { 7175 if (sibling.nodeType == 3) 7176 domRange[start ? 'setStart' : 'setEnd'](sibling, 0); 7177 else 7178 domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); 7179 7180 return; 7181 } 7182 7183 // Find the text node and offset 7184 while (sibling) { 7185 nodeValue = sibling.nodeValue; 7186 textNodeOffset += nodeValue.length; 7187 7188 // We are at or passed the position we where looking for 7189 if (textNodeOffset >= offset) { 7190 container = sibling; 7191 textNodeOffset -= offset; 7192 textNodeOffset = nodeValue.length - textNodeOffset; 7193 break; 7194 } 7195 7196 sibling = sibling.nextSibling; 7197 } 7198 } else { 7199 // Find the text node and offset 7200 sibling = container.previousSibling; 7201 7202 if (!sibling) 7203 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); 7204 7205 // If there isn't any text to loop then use the first position 7206 if (!offset) { 7207 if (container.nodeType == 3) 7208 domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); 7209 else 7210 domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); 7211 7212 return; 7213 } 7214 7215 while (sibling) { 7216 textNodeOffset += sibling.nodeValue.length; 7217 7218 // We are at or passed the position we where looking for 7219 if (textNodeOffset >= offset) { 7220 container = sibling; 7221 textNodeOffset -= offset; 7222 break; 7223 } 7224 7225 sibling = sibling.previousSibling; 7226 } 7227 } 7228 7229 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); 7230 }; 7231 7232 try { 7233 // Find start point 7234 findEndPoint(true); 7235 7236 // Find end point if needed 7237 if (!collapsed) 7238 findEndPoint(); 7239 } catch (ex) { 7240 // IE has a nasty bug where text nodes might throw "invalid argument" when you 7241 // access the nodeValue or other properties of text nodes. This seems to happend when 7242 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. 7243 if (ex.number == -2147024809) { 7244 // Get the current selection 7245 bookmark = self.getBookmark(2); 7246 7247 // Get start element 7248 tmpRange = ieRange.duplicate(); 7249 tmpRange.collapse(true); 7250 element = tmpRange.parentElement(); 7251 7252 // Get end element 7253 if (!collapsed) { 7254 tmpRange = ieRange.duplicate(); 7255 tmpRange.collapse(false); 7256 element2 = tmpRange.parentElement(); 7257 element2.innerHTML = element2.innerHTML; 7258 } 7259 7260 // Remove the broken elements 7261 element.innerHTML = element.innerHTML; 7262 7263 // Restore the selection 7264 self.moveToBookmark(bookmark); 7265 7266 // Since the range has moved we need to re-get it 7267 ieRange = selection.getRng(); 7268 7269 // Find start point 7270 findEndPoint(true); 7271 7272 // Find end point if needed 7273 if (!collapsed) 7274 findEndPoint(); 7275 } else 7276 throw ex; // Throw other errors 7277 } 7278 7279 return domRange; 7280 }; 7281 7282 this.getBookmark = function(type) { 7283 var rng = selection.getRng(), start, end, bookmark = {}; 7284 7285 function getIndexes(node) { 7286 var parent, root, children, i, indexes = []; 7287 7288 parent = node.parentNode; 7289 root = dom.getRoot().parentNode; 7290 7291 while (parent != root && parent.nodeType !== 9) { 7292 children = parent.children; 7293 7294 i = children.length; 7295 while (i--) { 7296 if (node === children[i]) { 7297 indexes.push(i); 7298 break; 7299 } 7300 } 7301 7302 node = parent; 7303 parent = parent.parentNode; 7304 } 7305 7306 return indexes; 7307 }; 7308 7309 function getBookmarkEndPoint(start) { 7310 var position; 7311 7312 position = getPosition(rng, start); 7313 if (position) { 7314 return { 7315 position : position.position, 7316 offset : position.offset, 7317 indexes : getIndexes(position.node), 7318 inside : position.inside 7319 }; 7320 } 7321 }; 7322 7323 // Non ubstructive bookmark 7324 if (type === 2) { 7325 // Handle text selection 7326 if (!rng.item) { 7327 bookmark.start = getBookmarkEndPoint(true); 7328 7329 if (!selection.isCollapsed()) 7330 bookmark.end = getBookmarkEndPoint(); 7331 } else 7332 bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))}; 7333 } 7334 7335 return bookmark; 7336 }; 7337 7338 this.moveToBookmark = function(bookmark) { 7339 var rng, body = dom.doc.body; 7340 7341 function resolveIndexes(indexes) { 7342 var node, i, idx, children; 7343 7344 node = dom.getRoot(); 7345 for (i = indexes.length - 1; i >= 0; i--) { 7346 children = node.children; 7347 idx = indexes[i]; 7348 7349 if (idx <= children.length - 1) { 7350 node = children[idx]; 7351 } 7352 } 7353 7354 return node; 7355 }; 7356 7357 function setBookmarkEndPoint(start) { 7358 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef; 7359 7360 if (endPoint) { 7361 moveLeft = endPoint.position > 0; 7362 7363 moveRng = body.createTextRange(); 7364 moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); 7365 7366 offset = endPoint.offset; 7367 if (offset !== undef) { 7368 moveRng.collapse(endPoint.inside || moveLeft); 7369 moveRng.moveStart('character', moveLeft ? -offset : offset); 7370 } else 7371 moveRng.collapse(start); 7372 7373 rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); 7374 7375 if (start) 7376 rng.collapse(true); 7377 } 7378 }; 7379 7380 if (bookmark.start) { 7381 if (bookmark.start.ctrl) { 7382 rng = body.createControlRange(); 7383 rng.addElement(resolveIndexes(bookmark.start.indexes)); 7384 rng.select(); 7385 } else { 7386 rng = body.createTextRange(); 7387 setBookmarkEndPoint(true); 7388 setBookmarkEndPoint(); 7389 rng.select(); 7390 } 7391 } 7392 }; 7393 7394 this.addRange = function(rng) { 7395 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, doc = selection.dom.doc, body = doc.body; 7396 7397 function setEndPoint(start) { 7398 var container, offset, marker, tmpRng, nodes; 7399 7400 marker = dom.create('a'); 7401 container = start ? startContainer : endContainer; 7402 offset = start ? startOffset : endOffset; 7403 tmpRng = ieRng.duplicate(); 7404 7405 if (container == doc || container == doc.documentElement) { 7406 container = body; 7407 offset = 0; 7408 } 7409 7410 if (container.nodeType == 3) { 7411 container.parentNode.insertBefore(marker, container); 7412 tmpRng.moveToElementText(marker); 7413 tmpRng.moveStart('character', offset); 7414 dom.remove(marker); 7415 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7416 } else { 7417 nodes = container.childNodes; 7418 7419 if (nodes.length) { 7420 if (offset >= nodes.length) { 7421 dom.insertAfter(marker, nodes[nodes.length - 1]); 7422 } else { 7423 container.insertBefore(marker, nodes[offset]); 7424 } 7425 7426 tmpRng.moveToElementText(marker); 7427 } else if (container.canHaveHTML) { 7428 // Empty node selection for example <div>|</div> 7429 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open 7430 container.innerHTML = '<span>\uFEFF</span>'; 7431 marker = container.firstChild; 7432 tmpRng.moveToElementText(marker); 7433 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason 7434 } 7435 7436 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7437 dom.remove(marker); 7438 } 7439 } 7440 7441 // Setup some shorter versions 7442 startContainer = rng.startContainer; 7443 startOffset = rng.startOffset; 7444 endContainer = rng.endContainer; 7445 endOffset = rng.endOffset; 7446 ieRng = body.createTextRange(); 7447 7448 // If single element selection then try making a control selection out of it 7449 if (startContainer == endContainer && startContainer.nodeType == 1) { 7450 // Trick to place the caret inside an empty block element like <p></p> 7451 if (startOffset == endOffset && !startContainer.hasChildNodes()) { 7452 if (startContainer.canHaveHTML) { 7453 // Check if previous sibling is an empty block if it is then we need to render it 7454 // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236 7455 // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p> 7456 sibling = startContainer.previousSibling; 7457 if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { 7458 sibling.innerHTML = '\uFEFF'; 7459 } else { 7460 sibling = null; 7461 } 7462 7463 startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>'; 7464 ieRng.moveToElementText(startContainer.lastChild); 7465 ieRng.select(); 7466 dom.doc.selection.clear(); 7467 startContainer.innerHTML = ''; 7468 7469 if (sibling) { 7470 sibling.innerHTML = ''; 7471 } 7472 return; 7473 } else { 7474 startOffset = dom.nodeIndex(startContainer); 7475 startContainer = startContainer.parentNode; 7476 } 7477 } 7478 7479 if (startOffset == endOffset - 1) { 7480 try { 7481 ctrlRng = body.createControlRange(); 7482 ctrlRng.addElement(startContainer.childNodes[startOffset]); 7483 ctrlRng.select(); 7484 return; 7485 } catch (ex) { 7486 // Ignore 7487 } 7488 } 7489 } 7490 7491 // Set start/end point of selection 7492 setEndPoint(true); 7493 setEndPoint(); 7494 7495 // Select the new range and scroll it into view 7496 ieRng.select(); 7497 }; 7498 7499 // Expose range method 7500 this.getRangeAt = getRange; 7501 }; 7502 7503 // Expose the selection object 7504 tinymce.dom.TridentSelection = Selection; 7505 })(); 7506 7507 7508 /* 7509 * Sizzle CSS Selector Engine 7510 * Copyright, The Dojo Foundation 7511 * Released under the MIT, BSD, and GPL Licenses. 7512 * More information: http://sizzlejs.com/ 7513 */ 7514 (function(){ 7515 7516 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 7517 expando = "sizcache", 7518 done = 0, 7519 toString = Object.prototype.toString, 7520 hasDuplicate = false, 7521 baseHasDuplicate = true, 7522 rBackslash = /\\/g, 7523 rReturn = /\r\n/g, 7524 rNonWord = /\W/; 7525 7526 // Here we check if the JavaScript engine is using some sort of 7527 // optimization where it does not always call our comparision 7528 // function. If that is the case, discard the hasDuplicate value. 7529 // Thus far that includes Google Chrome. 7530 [0, 0].sort(function() { 7531 baseHasDuplicate = false; 7532 return 0; 7533 }); 7534 7535 var Sizzle = function( selector, context, results, seed ) { 7536 results = results || []; 7537 context = context || document; 7538 7539 var origContext = context; 7540 7541 if ( context.nodeType !== 1 && context.nodeType !== 9 ) { 7542 return []; 7543 } 7544 7545 if ( !selector || typeof selector !== "string" ) { 7546 return results; 7547 } 7548 7549 var m, set, checkSet, extra, ret, cur, pop, i, 7550 prune = true, 7551 contextXML = Sizzle.isXML( context ), 7552 parts = [], 7553 soFar = selector; 7554 7555 // Reset the position of the chunker regexp (start from head) 7556 do { 7557 chunker.exec( "" ); 7558 m = chunker.exec( soFar ); 7559 7560 if ( m ) { 7561 soFar = m[3]; 7562 7563 parts.push( m[1] ); 7564 7565 if ( m[2] ) { 7566 extra = m[3]; 7567 break; 7568 } 7569 } 7570 } while ( m ); 7571 7572 if ( parts.length > 1 && origPOS.exec( selector ) ) { 7573 7574 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { 7575 set = posProcess( parts[0] + parts[1], context, seed ); 7576 7577 } else { 7578 set = Expr.relative[ parts[0] ] ? 7579 [ context ] : 7580 Sizzle( parts.shift(), context ); 7581 7582 while ( parts.length ) { 7583 selector = parts.shift(); 7584 7585 if ( Expr.relative[ selector ] ) { 7586 selector += parts.shift(); 7587 } 7588 7589 set = posProcess( selector, set, seed ); 7590 } 7591 } 7592 7593 } else { 7594 // Take a shortcut and set the context if the root selector is an ID 7595 // (but not if it'll be faster if the inner selector is an ID) 7596 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && 7597 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { 7598 7599 ret = Sizzle.find( parts.shift(), context, contextXML ); 7600 context = ret.expr ? 7601 Sizzle.filter( ret.expr, ret.set )[0] : 7602 ret.set[0]; 7603 } 7604 7605 if ( context ) { 7606 ret = seed ? 7607 { expr: parts.pop(), set: makeArray(seed) } : 7608 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); 7609 7610 set = ret.expr ? 7611 Sizzle.filter( ret.expr, ret.set ) : 7612 ret.set; 7613 7614 if ( parts.length > 0 ) { 7615 checkSet = makeArray( set ); 7616 7617 } else { 7618 prune = false; 7619 } 7620 7621 while ( parts.length ) { 7622 cur = parts.pop(); 7623 pop = cur; 7624 7625 if ( !Expr.relative[ cur ] ) { 7626 cur = ""; 7627 } else { 7628 pop = parts.pop(); 7629 } 7630 7631 if ( pop == null ) { 7632 pop = context; 7633 } 7634 7635 Expr.relative[ cur ]( checkSet, pop, contextXML ); 7636 } 7637 7638 } else { 7639 checkSet = parts = []; 7640 } 7641 } 7642 7643 if ( !checkSet ) { 7644 checkSet = set; 7645 } 7646 7647 if ( !checkSet ) { 7648 Sizzle.error( cur || selector ); 7649 } 7650 7651 if ( toString.call(checkSet) === "[object Array]" ) { 7652 if ( !prune ) { 7653 results.push.apply( results, checkSet ); 7654 7655 } else if ( context && context.nodeType === 1 ) { 7656 for ( i = 0; checkSet[i] != null; i++ ) { 7657 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { 7658 results.push( set[i] ); 7659 } 7660 } 7661 7662 } else { 7663 for ( i = 0; checkSet[i] != null; i++ ) { 7664 if ( checkSet[i] && checkSet[i].nodeType === 1 ) { 7665 results.push( set[i] ); 7666 } 7667 } 7668 } 7669 7670 } else { 7671 makeArray( checkSet, results ); 7672 } 7673 7674 if ( extra ) { 7675 Sizzle( extra, origContext, results, seed ); 7676 Sizzle.uniqueSort( results ); 7677 } 7678 7679 return results; 7680 }; 7681 7682 Sizzle.uniqueSort = function( results ) { 7683 if ( sortOrder ) { 7684 hasDuplicate = baseHasDuplicate; 7685 results.sort( sortOrder ); 7686 7687 if ( hasDuplicate ) { 7688 for ( var i = 1; i < results.length; i++ ) { 7689 if ( results[i] === results[ i - 1 ] ) { 7690 results.splice( i--, 1 ); 7691 } 7692 } 7693 } 7694 } 7695 7696 return results; 7697 }; 7698 7699 Sizzle.matches = function( expr, set ) { 7700 return Sizzle( expr, null, null, set ); 7701 }; 7702 7703 Sizzle.matchesSelector = function( node, expr ) { 7704 return Sizzle( expr, null, null, [node] ).length > 0; 7705 }; 7706 7707 Sizzle.find = function( expr, context, isXML ) { 7708 var set, i, len, match, type, left; 7709 7710 if ( !expr ) { 7711 return []; 7712 } 7713 7714 for ( i = 0, len = Expr.order.length; i < len; i++ ) { 7715 type = Expr.order[i]; 7716 7717 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { 7718 left = match[1]; 7719 match.splice( 1, 1 ); 7720 7721 if ( left.substr( left.length - 1 ) !== "\\" ) { 7722 match[1] = (match[1] || "").replace( rBackslash, "" ); 7723 set = Expr.find[ type ]( match, context, isXML ); 7724 7725 if ( set != null ) { 7726 expr = expr.replace( Expr.match[ type ], "" ); 7727 break; 7728 } 7729 } 7730 } 7731 } 7732 7733 if ( !set ) { 7734 set = typeof context.getElementsByTagName !== "undefined" ? 7735 context.getElementsByTagName( "*" ) : 7736 []; 7737 } 7738 7739 return { set: set, expr: expr }; 7740 }; 7741 7742 Sizzle.filter = function( expr, set, inplace, not ) { 7743 var match, anyFound, 7744 type, found, item, filter, left, 7745 i, pass, 7746 old = expr, 7747 result = [], 7748 curLoop = set, 7749 isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); 7750 7751 while ( expr && set.length ) { 7752 for ( type in Expr.filter ) { 7753 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { 7754 filter = Expr.filter[ type ]; 7755 left = match[1]; 7756 7757 anyFound = false; 7758 7759 match.splice(1,1); 7760 7761 if ( left.substr( left.length - 1 ) === "\\" ) { 7762 continue; 7763 } 7764 7765 if ( curLoop === result ) { 7766 result = []; 7767 } 7768 7769 if ( Expr.preFilter[ type ] ) { 7770 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); 7771 7772 if ( !match ) { 7773 anyFound = found = true; 7774 7775 } else if ( match === true ) { 7776 continue; 7777 } 7778 } 7779 7780 if ( match ) { 7781 for ( i = 0; (item = curLoop[i]) != null; i++ ) { 7782 if ( item ) { 7783 found = filter( item, match, i, curLoop ); 7784 pass = not ^ found; 7785 7786 if ( inplace && found != null ) { 7787 if ( pass ) { 7788 anyFound = true; 7789 7790 } else { 7791 curLoop[i] = false; 7792 } 7793 7794 } else if ( pass ) { 7795 result.push( item ); 7796 anyFound = true; 7797 } 7798 } 7799 } 7800 } 7801 7802 if ( found !== undefined ) { 7803 if ( !inplace ) { 7804 curLoop = result; 7805 } 7806 7807 expr = expr.replace( Expr.match[ type ], "" ); 7808 7809 if ( !anyFound ) { 7810 return []; 7811 } 7812 7813 break; 7814 } 7815 } 7816 } 7817 7818 // Improper expression 7819 if ( expr === old ) { 7820 if ( anyFound == null ) { 7821 Sizzle.error( expr ); 7822 7823 } else { 7824 break; 7825 } 7826 } 7827 7828 old = expr; 7829 } 7830 7831 return curLoop; 7832 }; 7833 7834 Sizzle.error = function( msg ) { 7835 throw new Error( "Syntax error, unrecognized expression: " + msg ); 7836 }; 7837 7838 var getText = Sizzle.getText = function( elem ) { 7839 var i, node, 7840 nodeType = elem.nodeType, 7841 ret = ""; 7842 7843 if ( nodeType ) { 7844 if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { 7845 // Use textContent || innerText for elements 7846 if ( typeof elem.textContent === 'string' ) { 7847 return elem.textContent; 7848 } else if ( typeof elem.innerText === 'string' ) { 7849 // Replace IE's carriage returns 7850 return elem.innerText.replace( rReturn, '' ); 7851 } else { 7852 // Traverse it's children 7853 for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { 7854 ret += getText( elem ); 7855 } 7856 } 7857 } else if ( nodeType === 3 || nodeType === 4 ) { 7858 return elem.nodeValue; 7859 } 7860 } else { 7861 7862 // If no nodeType, this is expected to be an array 7863 for ( i = 0; (node = elem[i]); i++ ) { 7864 // Do not traverse comment nodes 7865 if ( node.nodeType !== 8 ) { 7866 ret += getText( node ); 7867 } 7868 } 7869 } 7870 return ret; 7871 }; 7872 7873 var Expr = Sizzle.selectors = { 7874 order: [ "ID", "NAME", "TAG" ], 7875 7876 match: { 7877 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 7878 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 7879 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, 7880 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, 7881 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, 7882 CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, 7883 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, 7884 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ 7885 }, 7886 7887 leftMatch: {}, 7888 7889 attrMap: { 7890 "class": "className", 7891 "for": "htmlFor" 7892 }, 7893 7894 attrHandle: { 7895 href: function( elem ) { 7896 return elem.getAttribute( "href" ); 7897 }, 7898 type: function( elem ) { 7899 return elem.getAttribute( "type" ); 7900 } 7901 }, 7902 7903 relative: { 7904 "+": function(checkSet, part){ 7905 var isPartStr = typeof part === "string", 7906 isTag = isPartStr && !rNonWord.test( part ), 7907 isPartStrNotTag = isPartStr && !isTag; 7908 7909 if ( isTag ) { 7910 part = part.toLowerCase(); 7911 } 7912 7913 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { 7914 if ( (elem = checkSet[i]) ) { 7915 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} 7916 7917 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? 7918 elem || false : 7919 elem === part; 7920 } 7921 } 7922 7923 if ( isPartStrNotTag ) { 7924 Sizzle.filter( part, checkSet, true ); 7925 } 7926 }, 7927 7928 ">": function( checkSet, part ) { 7929 var elem, 7930 isPartStr = typeof part === "string", 7931 i = 0, 7932 l = checkSet.length; 7933 7934 if ( isPartStr && !rNonWord.test( part ) ) { 7935 part = part.toLowerCase(); 7936 7937 for ( ; i < l; i++ ) { 7938 elem = checkSet[i]; 7939 7940 if ( elem ) { 7941 var parent = elem.parentNode; 7942 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; 7943 } 7944 } 7945 7946 } else { 7947 for ( ; i < l; i++ ) { 7948 elem = checkSet[i]; 7949 7950 if ( elem ) { 7951 checkSet[i] = isPartStr ? 7952 elem.parentNode : 7953 elem.parentNode === part; 7954 } 7955 } 7956 7957 if ( isPartStr ) { 7958 Sizzle.filter( part, checkSet, true ); 7959 } 7960 } 7961 }, 7962 7963 "": function(checkSet, part, isXML){ 7964 var nodeCheck, 7965 doneName = done++, 7966 checkFn = dirCheck; 7967 7968 if ( typeof part === "string" && !rNonWord.test( part ) ) { 7969 part = part.toLowerCase(); 7970 nodeCheck = part; 7971 checkFn = dirNodeCheck; 7972 } 7973 7974 checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); 7975 }, 7976 7977 "~": function( checkSet, part, isXML ) { 7978 var nodeCheck, 7979 doneName = done++, 7980 checkFn = dirCheck; 7981 7982 if ( typeof part === "string" && !rNonWord.test( part ) ) { 7983 part = part.toLowerCase(); 7984 nodeCheck = part; 7985 checkFn = dirNodeCheck; 7986 } 7987 7988 checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); 7989 } 7990 }, 7991 7992 find: { 7993 ID: function( match, context, isXML ) { 7994 if ( typeof context.getElementById !== "undefined" && !isXML ) { 7995 var m = context.getElementById(match[1]); 7996 // Check parentNode to catch when Blackberry 4.6 returns 7997 // nodes that are no longer in the document #6963 7998 return m && m.parentNode ? [m] : []; 7999 } 8000 }, 8001 8002 NAME: function( match, context ) { 8003 if ( typeof context.getElementsByName !== "undefined" ) { 8004 var ret = [], 8005 results = context.getElementsByName( match[1] ); 8006 8007 for ( var i = 0, l = results.length; i < l; i++ ) { 8008 if ( results[i].getAttribute("name") === match[1] ) { 8009 ret.push( results[i] ); 8010 } 8011 } 8012 8013 return ret.length === 0 ? null : ret; 8014 } 8015 }, 8016 8017 TAG: function( match, context ) { 8018 if ( typeof context.getElementsByTagName !== "undefined" ) { 8019 return context.getElementsByTagName( match[1] ); 8020 } 8021 } 8022 }, 8023 preFilter: { 8024 CLASS: function( match, curLoop, inplace, result, not, isXML ) { 8025 match = " " + match[1].replace( rBackslash, "" ) + " "; 8026 8027 if ( isXML ) { 8028 return match; 8029 } 8030 8031 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { 8032 if ( elem ) { 8033 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { 8034 if ( !inplace ) { 8035 result.push( elem ); 8036 } 8037 8038 } else if ( inplace ) { 8039 curLoop[i] = false; 8040 } 8041 } 8042 } 8043 8044 return false; 8045 }, 8046 8047 ID: function( match ) { 8048 return match[1].replace( rBackslash, "" ); 8049 }, 8050 8051 TAG: function( match, curLoop ) { 8052 return match[1].replace( rBackslash, "" ).toLowerCase(); 8053 }, 8054 8055 CHILD: function( match ) { 8056 if ( match[1] === "nth" ) { 8057 if ( !match[2] ) { 8058 Sizzle.error( match[0] ); 8059 } 8060 8061 match[2] = match[2].replace(/^\+|\s*/g, ''); 8062 8063 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' 8064 var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( 8065 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || 8066 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); 8067 8068 // calculate the numbers (first)n+(last) including if they are negative 8069 match[2] = (test[1] + (test[2] || 1)) - 0; 8070 match[3] = test[3] - 0; 8071 } 8072 else if ( match[2] ) { 8073 Sizzle.error( match[0] ); 8074 } 8075 8076 // TODO: Move to normal caching system 8077 match[0] = done++; 8078 8079 return match; 8080 }, 8081 8082 ATTR: function( match, curLoop, inplace, result, not, isXML ) { 8083 var name = match[1] = match[1].replace( rBackslash, "" ); 8084 8085 if ( !isXML && Expr.attrMap[name] ) { 8086 match[1] = Expr.attrMap[name]; 8087 } 8088 8089 // Handle if an un-quoted value was used 8090 match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); 8091 8092 if ( match[2] === "~=" ) { 8093 match[4] = " " + match[4] + " "; 8094 } 8095 8096 return match; 8097 }, 8098 8099 PSEUDO: function( match, curLoop, inplace, result, not ) { 8100 if ( match[1] === "not" ) { 8101 // If we're dealing with a complex expression, or a simple one 8102 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { 8103 match[3] = Sizzle(match[3], null, null, curLoop); 8104 8105 } else { 8106 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); 8107 8108 if ( !inplace ) { 8109 result.push.apply( result, ret ); 8110 } 8111 8112 return false; 8113 } 8114 8115 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { 8116 return true; 8117 } 8118 8119 return match; 8120 }, 8121 8122 POS: function( match ) { 8123 match.unshift( true ); 8124 8125 return match; 8126 } 8127 }, 8128 8129 filters: { 8130 enabled: function( elem ) { 8131 return elem.disabled === false && elem.type !== "hidden"; 8132 }, 8133 8134 disabled: function( elem ) { 8135 return elem.disabled === true; 8136 }, 8137 8138 checked: function( elem ) { 8139 return elem.checked === true; 8140 }, 8141 8142 selected: function( elem ) { 8143 // Accessing this property makes selected-by-default 8144 // options in Safari work properly 8145 if ( elem.parentNode ) { 8146 elem.parentNode.selectedIndex; 8147 } 8148 8149 return elem.selected === true; 8150 }, 8151 8152 parent: function( elem ) { 8153 return !!elem.firstChild; 8154 }, 8155 8156 empty: function( elem ) { 8157 return !elem.firstChild; 8158 }, 8159 8160 has: function( elem, i, match ) { 8161 return !!Sizzle( match[3], elem ).length; 8162 }, 8163 8164 header: function( elem ) { 8165 return (/h\d/i).test( elem.nodeName ); 8166 }, 8167 8168 text: function( elem ) { 8169 var attr = elem.getAttribute( "type" ), type = elem.type; 8170 // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) 8171 // use getAttribute instead to test this case 8172 return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); 8173 }, 8174 8175 radio: function( elem ) { 8176 return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; 8177 }, 8178 8179 checkbox: function( elem ) { 8180 return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; 8181 }, 8182 8183 file: function( elem ) { 8184 return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; 8185 }, 8186 8187 password: function( elem ) { 8188 return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; 8189 }, 8190 8191 submit: function( elem ) { 8192 var name = elem.nodeName.toLowerCase(); 8193 return (name === "input" || name === "button") && "submit" === elem.type; 8194 }, 8195 8196 image: function( elem ) { 8197 return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; 8198 }, 8199 8200 reset: function( elem ) { 8201 var name = elem.nodeName.toLowerCase(); 8202 return (name === "input" || name === "button") && "reset" === elem.type; 8203 }, 8204 8205 button: function( elem ) { 8206 var name = elem.nodeName.toLowerCase(); 8207 return name === "input" && "button" === elem.type || name === "button"; 8208 }, 8209 8210 input: function( elem ) { 8211 return (/input|select|textarea|button/i).test( elem.nodeName ); 8212 }, 8213 8214 focus: function( elem ) { 8215 return elem === elem.ownerDocument.activeElement; 8216 } 8217 }, 8218 setFilters: { 8219 first: function( elem, i ) { 8220 return i === 0; 8221 }, 8222 8223 last: function( elem, i, match, array ) { 8224 return i === array.length - 1; 8225 }, 8226 8227 even: function( elem, i ) { 8228 return i % 2 === 0; 8229 }, 8230 8231 odd: function( elem, i ) { 8232 return i % 2 === 1; 8233 }, 8234 8235 lt: function( elem, i, match ) { 8236 return i < match[3] - 0; 8237 }, 8238 8239 gt: function( elem, i, match ) { 8240 return i > match[3] - 0; 8241 }, 8242 8243 nth: function( elem, i, match ) { 8244 return match[3] - 0 === i; 8245 }, 8246 8247 eq: function( elem, i, match ) { 8248 return match[3] - 0 === i; 8249 } 8250 }, 8251 filter: { 8252 PSEUDO: function( elem, match, i, array ) { 8253 var name = match[1], 8254 filter = Expr.filters[ name ]; 8255 8256 if ( filter ) { 8257 return filter( elem, i, match, array ); 8258 8259 } else if ( name === "contains" ) { 8260 return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; 8261 8262 } else if ( name === "not" ) { 8263 var not = match[3]; 8264 8265 for ( var j = 0, l = not.length; j < l; j++ ) { 8266 if ( not[j] === elem ) { 8267 return false; 8268 } 8269 } 8270 8271 return true; 8272 8273 } else { 8274 Sizzle.error( name ); 8275 } 8276 }, 8277 8278 CHILD: function( elem, match ) { 8279 var first, last, 8280 doneName, parent, cache, 8281 count, diff, 8282 type = match[1], 8283 node = elem; 8284 8285 switch ( type ) { 8286 case "only": 8287 case "first": 8288 while ( (node = node.previousSibling) ) { 8289 if ( node.nodeType === 1 ) { 8290 return false; 8291 } 8292 } 8293 8294 if ( type === "first" ) { 8295 return true; 8296 } 8297 8298 node = elem; 8299 8300 /* falls through */ 8301 case "last": 8302 while ( (node = node.nextSibling) ) { 8303 if ( node.nodeType === 1 ) { 8304 return false; 8305 } 8306 } 8307 8308 return true; 8309 8310 case "nth": 8311 first = match[2]; 8312 last = match[3]; 8313 8314 if ( first === 1 && last === 0 ) { 8315 return true; 8316 } 8317 8318 doneName = match[0]; 8319 parent = elem.parentNode; 8320 8321 if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { 8322 count = 0; 8323 8324 for ( node = parent.firstChild; node; node = node.nextSibling ) { 8325 if ( node.nodeType === 1 ) { 8326 node.nodeIndex = ++count; 8327 } 8328 } 8329 8330 parent[ expando ] = doneName; 8331 } 8332 8333 diff = elem.nodeIndex - last; 8334 8335 if ( first === 0 ) { 8336 return diff === 0; 8337 8338 } else { 8339 return ( diff % first === 0 && diff / first >= 0 ); 8340 } 8341 } 8342 }, 8343 8344 ID: function( elem, match ) { 8345 return elem.nodeType === 1 && elem.getAttribute("id") === match; 8346 }, 8347 8348 TAG: function( elem, match ) { 8349 return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; 8350 }, 8351 8352 CLASS: function( elem, match ) { 8353 return (" " + (elem.className || elem.getAttribute("class")) + " ") 8354 .indexOf( match ) > -1; 8355 }, 8356 8357 ATTR: function( elem, match ) { 8358 var name = match[1], 8359 result = Sizzle.attr ? 8360 Sizzle.attr( elem, name ) : 8361 Expr.attrHandle[ name ] ? 8362 Expr.attrHandle[ name ]( elem ) : 8363 elem[ name ] != null ? 8364 elem[ name ] : 8365 elem.getAttribute( name ), 8366 value = result + "", 8367 type = match[2], 8368 check = match[4]; 8369 8370 return result == null ? 8371 type === "!=" : 8372 !type && Sizzle.attr ? 8373 result != null : 8374 type === "=" ? 8375 value === check : 8376 type === "*=" ? 8377 value.indexOf(check) >= 0 : 8378 type === "~=" ? 8379 (" " + value + " ").indexOf(check) >= 0 : 8380 !check ? 8381 value && result !== false : 8382 type === "!=" ? 8383 value !== check : 8384 type === "^=" ? 8385 value.indexOf(check) === 0 : 8386 type === "$=" ? 8387 value.substr(value.length - check.length) === check : 8388 type === "|=" ? 8389 value === check || value.substr(0, check.length + 1) === check + "-" : 8390 false; 8391 }, 8392 8393 POS: function( elem, match, i, array ) { 8394 var name = match[2], 8395 filter = Expr.setFilters[ name ]; 8396 8397 if ( filter ) { 8398 return filter( elem, i, match, array ); 8399 } 8400 } 8401 } 8402 }; 8403 8404 var origPOS = Expr.match.POS, 8405 fescape = function(all, num){ 8406 return "\\" + (num - 0 + 1); 8407 }; 8408 8409 for ( var type in Expr.match ) { 8410 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); 8411 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); 8412 } 8413 // Expose origPOS 8414 // "global" as in regardless of relation to brackets/parens 8415 Expr.match.globalPOS = origPOS; 8416 8417 var makeArray = function( array, results ) { 8418 array = Array.prototype.slice.call( array, 0 ); 8419 8420 if ( results ) { 8421 results.push.apply( results, array ); 8422 return results; 8423 } 8424 8425 return array; 8426 }; 8427 8428 // Perform a simple check to determine if the browser is capable of 8429 // converting a NodeList to an array using builtin methods. 8430 // Also verifies that the returned array holds DOM nodes 8431 // (which is not the case in the Blackberry browser) 8432 try { 8433 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; 8434 8435 // Provide a fallback method if it does not work 8436 } catch( e ) { 8437 makeArray = function( array, results ) { 8438 var i = 0, 8439 ret = results || []; 8440 8441 if ( toString.call(array) === "[object Array]" ) { 8442 Array.prototype.push.apply( ret, array ); 8443 8444 } else { 8445 if ( typeof array.length === "number" ) { 8446 for ( var l = array.length; i < l; i++ ) { 8447 ret.push( array[i] ); 8448 } 8449 8450 } else { 8451 for ( ; array[i]; i++ ) { 8452 ret.push( array[i] ); 8453 } 8454 } 8455 } 8456 8457 return ret; 8458 }; 8459 } 8460 8461 var sortOrder, siblingCheck; 8462 8463 if ( document.documentElement.compareDocumentPosition ) { 8464 sortOrder = function( a, b ) { 8465 if ( a === b ) { 8466 hasDuplicate = true; 8467 return 0; 8468 } 8469 8470 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { 8471 return a.compareDocumentPosition ? -1 : 1; 8472 } 8473 8474 return a.compareDocumentPosition(b) & 4 ? -1 : 1; 8475 }; 8476 8477 } else { 8478 sortOrder = function( a, b ) { 8479 // The nodes are identical, we can exit early 8480 if ( a === b ) { 8481 hasDuplicate = true; 8482 return 0; 8483 8484 // Fallback to using sourceIndex (in IE) if it's available on both nodes 8485 } else if ( a.sourceIndex && b.sourceIndex ) { 8486 return a.sourceIndex - b.sourceIndex; 8487 } 8488 8489 var al, bl, 8490 ap = [], 8491 bp = [], 8492 aup = a.parentNode, 8493 bup = b.parentNode, 8494 cur = aup; 8495 8496 // If the nodes are siblings (or identical) we can do a quick check 8497 if ( aup === bup ) { 8498 return siblingCheck( a, b ); 8499 8500 // If no parents were found then the nodes are disconnected 8501 } else if ( !aup ) { 8502 return -1; 8503 8504 } else if ( !bup ) { 8505 return 1; 8506 } 8507 8508 // Otherwise they're somewhere else in the tree so we need 8509 // to build up a full list of the parentNodes for comparison 8510 while ( cur ) { 8511 ap.unshift( cur ); 8512 cur = cur.parentNode; 8513 } 8514 8515 cur = bup; 8516 8517 while ( cur ) { 8518 bp.unshift( cur ); 8519 cur = cur.parentNode; 8520 } 8521 8522 al = ap.length; 8523 bl = bp.length; 8524 8525 // Start walking down the tree looking for a discrepancy 8526 for ( var i = 0; i < al && i < bl; i++ ) { 8527 if ( ap[i] !== bp[i] ) { 8528 return siblingCheck( ap[i], bp[i] ); 8529 } 8530 } 8531 8532 // We ended someplace up the tree so do a sibling check 8533 return i === al ? 8534 siblingCheck( a, bp[i], -1 ) : 8535 siblingCheck( ap[i], b, 1 ); 8536 }; 8537 8538 siblingCheck = function( a, b, ret ) { 8539 if ( a === b ) { 8540 return ret; 8541 } 8542 8543 var cur = a.nextSibling; 8544 8545 while ( cur ) { 8546 if ( cur === b ) { 8547 return -1; 8548 } 8549 8550 cur = cur.nextSibling; 8551 } 8552 8553 return 1; 8554 }; 8555 } 8556 8557 // Check to see if the browser returns elements by name when 8558 // querying by getElementById (and provide a workaround) 8559 (function(){ 8560 // We're going to inject a fake input element with a specified name 8561 var form = document.createElement("div"), 8562 id = "script" + (new Date()).getTime(), 8563 root = document.documentElement; 8564 8565 form.innerHTML = "<a name='" + id + "'/>"; 8566 8567 // Inject it into the root element, check its status, and remove it quickly 8568 root.insertBefore( form, root.firstChild ); 8569 8570 // The workaround has to do additional checks after a getElementById 8571 // Which slows things down for other browsers (hence the branching) 8572 if ( document.getElementById( id ) ) { 8573 Expr.find.ID = function( match, context, isXML ) { 8574 if ( typeof context.getElementById !== "undefined" && !isXML ) { 8575 var m = context.getElementById(match[1]); 8576 8577 return m ? 8578 m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? 8579 [m] : 8580 undefined : 8581 []; 8582 } 8583 }; 8584 8585 Expr.filter.ID = function( elem, match ) { 8586 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); 8587 8588 return elem.nodeType === 1 && node && node.nodeValue === match; 8589 }; 8590 } 8591 8592 root.removeChild( form ); 8593 8594 // release memory in IE 8595 root = form = null; 8596 })(); 8597 8598 (function(){ 8599 // Check to see if the browser returns only elements 8600 // when doing getElementsByTagName("*") 8601 8602 // Create a fake element 8603 var div = document.createElement("div"); 8604 div.appendChild( document.createComment("") ); 8605 8606 // Make sure no comments are found 8607 if ( div.getElementsByTagName("*").length > 0 ) { 8608 Expr.find.TAG = function( match, context ) { 8609 var results = context.getElementsByTagName( match[1] ); 8610 8611 // Filter out possible comments 8612 if ( match[1] === "*" ) { 8613 var tmp = []; 8614 8615 for ( var i = 0; results[i]; i++ ) { 8616 if ( results[i].nodeType === 1 ) { 8617 tmp.push( results[i] ); 8618 } 8619 } 8620 8621 results = tmp; 8622 } 8623 8624 return results; 8625 }; 8626 } 8627 8628 // Check to see if an attribute returns normalized href attributes 8629 div.innerHTML = "<a href='#'></a>"; 8630 8631 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && 8632 div.firstChild.getAttribute("href") !== "#" ) { 8633 8634 Expr.attrHandle.href = function( elem ) { 8635 return elem.getAttribute( "href", 2 ); 8636 }; 8637 } 8638 8639 // release memory in IE 8640 div = null; 8641 })(); 8642 8643 if ( document.querySelectorAll ) { 8644 (function(){ 8645 var oldSizzle = Sizzle, 8646 div = document.createElement("div"), 8647 id = "__sizzle__"; 8648 8649 div.innerHTML = "<p class='TEST'></p>"; 8650 8651 // Safari can't handle uppercase or unicode characters when 8652 // in quirks mode. 8653 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { 8654 return; 8655 } 8656 8657 Sizzle = function( query, context, extra, seed ) { 8658 context = context || document; 8659 8660 // Only use querySelectorAll on non-XML documents 8661 // (ID selectors don't work in non-HTML documents) 8662 if ( !seed && !Sizzle.isXML(context) ) { 8663 // See if we find a selector to speed up 8664 var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); 8665 8666 if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { 8667 // Speed-up: Sizzle("TAG") 8668 if ( match[1] ) { 8669 return makeArray( context.getElementsByTagName( query ), extra ); 8670 8671 // Speed-up: Sizzle(".CLASS") 8672 } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { 8673 return makeArray( context.getElementsByClassName( match[2] ), extra ); 8674 } 8675 } 8676 8677 if ( context.nodeType === 9 ) { 8678 // Speed-up: Sizzle("body") 8679 // The body element only exists once, optimize finding it 8680 if ( query === "body" && context.body ) { 8681 return makeArray( [ context.body ], extra ); 8682 8683 // Speed-up: Sizzle("#ID") 8684 } else if ( match && match[3] ) { 8685 var elem = context.getElementById( match[3] ); 8686 8687 // Check parentNode to catch when Blackberry 4.6 returns 8688 // nodes that are no longer in the document #6963 8689 if ( elem && elem.parentNode ) { 8690 // Handle the case where IE and Opera return items 8691 // by name instead of ID 8692 if ( elem.id === match[3] ) { 8693 return makeArray( [ elem ], extra ); 8694 } 8695 8696 } else { 8697 return makeArray( [], extra ); 8698 } 8699 } 8700 8701 try { 8702 return makeArray( context.querySelectorAll(query), extra ); 8703 } catch(qsaError) {} 8704 8705 // qSA works strangely on Element-rooted queries 8706 // We can work around this by specifying an extra ID on the root 8707 // and working up from there (Thanks to Andrew Dupont for the technique) 8708 // IE 8 doesn't work on object elements 8709 } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { 8710 var oldContext = context, 8711 old = context.getAttribute( "id" ), 8712 nid = old || id, 8713 hasParent = context.parentNode, 8714 relativeHierarchySelector = /^\s*[+~]/.test( query ); 8715 8716 if ( !old ) { 8717 context.setAttribute( "id", nid ); 8718 } else { 8719 nid = nid.replace( /'/g, "\\$&" ); 8720 } 8721 if ( relativeHierarchySelector && hasParent ) { 8722 context = context.parentNode; 8723 } 8724 8725 try { 8726 if ( !relativeHierarchySelector || hasParent ) { 8727 return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); 8728 } 8729 8730 } catch(pseudoError) { 8731 } finally { 8732 if ( !old ) { 8733 oldContext.removeAttribute( "id" ); 8734 } 8735 } 8736 } 8737 } 8738 8739 return oldSizzle(query, context, extra, seed); 8740 }; 8741 8742 for ( var prop in oldSizzle ) { 8743 Sizzle[ prop ] = oldSizzle[ prop ]; 8744 } 8745 8746 // release memory in IE 8747 div = null; 8748 })(); 8749 } 8750 8751 (function(){ 8752 var html = document.documentElement, 8753 matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; 8754 8755 if ( matches ) { 8756 // Check to see if it's possible to do matchesSelector 8757 // on a disconnected node (IE 9 fails this) 8758 var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), 8759 pseudoWorks = false; 8760 8761 try { 8762 // This should fail with an exception 8763 // Gecko does not error, returns false instead 8764 matches.call( document.documentElement, "[test!='']:sizzle" ); 8765 8766 } catch( pseudoError ) { 8767 pseudoWorks = true; 8768 } 8769 8770 Sizzle.matchesSelector = function( node, expr ) { 8771 // Make sure that attribute selectors are quoted 8772 expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); 8773 8774 if ( !Sizzle.isXML( node ) ) { 8775 try { 8776 if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { 8777 var ret = matches.call( node, expr ); 8778 8779 // IE 9's matchesSelector returns false on disconnected nodes 8780 if ( ret || !disconnectedMatch || 8781 // As well, disconnected nodes are said to be in a document 8782 // fragment in IE 9, so check for that 8783 node.document && node.document.nodeType !== 11 ) { 8784 return ret; 8785 } 8786 } 8787 } catch(e) {} 8788 } 8789 8790 return Sizzle(expr, null, null, [node]).length > 0; 8791 }; 8792 } 8793 })(); 8794 8795 (function(){ 8796 var div = document.createElement("div"); 8797 8798 div.innerHTML = "<div class='test e'></div><div class='test'></div>"; 8799 8800 // Opera can't find a second classname (in 9.6) 8801 // Also, make sure that getElementsByClassName actually exists 8802 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { 8803 return; 8804 } 8805 8806 // Safari caches class attributes, doesn't catch changes (in 3.2) 8807 div.lastChild.className = "e"; 8808 8809 if ( div.getElementsByClassName("e").length === 1 ) { 8810 return; 8811 } 8812 8813 Expr.order.splice(1, 0, "CLASS"); 8814 Expr.find.CLASS = function( match, context, isXML ) { 8815 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { 8816 return context.getElementsByClassName(match[1]); 8817 } 8818 }; 8819 8820 // release memory in IE 8821 div = null; 8822 })(); 8823 8824 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 8825 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 8826 var elem = checkSet[i]; 8827 8828 if ( elem ) { 8829 var match = false; 8830 8831 elem = elem[dir]; 8832 8833 while ( elem ) { 8834 if ( elem[ expando ] === doneName ) { 8835 match = checkSet[elem.sizset]; 8836 break; 8837 } 8838 8839 if ( elem.nodeType === 1 && !isXML ){ 8840 elem[ expando ] = doneName; 8841 elem.sizset = i; 8842 } 8843 8844 if ( elem.nodeName.toLowerCase() === cur ) { 8845 match = elem; 8846 break; 8847 } 8848 8849 elem = elem[dir]; 8850 } 8851 8852 checkSet[i] = match; 8853 } 8854 } 8855 } 8856 8857 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 8858 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 8859 var elem = checkSet[i]; 8860 8861 if ( elem ) { 8862 var match = false; 8863 8864 elem = elem[dir]; 8865 8866 while ( elem ) { 8867 if ( elem[ expando ] === doneName ) { 8868 match = checkSet[elem.sizset]; 8869 break; 8870 } 8871 8872 if ( elem.nodeType === 1 ) { 8873 if ( !isXML ) { 8874 elem[ expando ] = doneName; 8875 elem.sizset = i; 8876 } 8877 8878 if ( typeof cur !== "string" ) { 8879 if ( elem === cur ) { 8880 match = true; 8881 break; 8882 } 8883 8884 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { 8885 match = elem; 8886 break; 8887 } 8888 } 8889 8890 elem = elem[dir]; 8891 } 8892 8893 checkSet[i] = match; 8894 } 8895 } 8896 } 8897 8898 if ( document.documentElement.contains ) { 8899 Sizzle.contains = function( a, b ) { 8900 return a !== b && (a.contains ? a.contains(b) : true); 8901 }; 8902 8903 } else if ( document.documentElement.compareDocumentPosition ) { 8904 Sizzle.contains = function( a, b ) { 8905 return !!(a.compareDocumentPosition(b) & 16); 8906 }; 8907 8908 } else { 8909 Sizzle.contains = function() { 8910 return false; 8911 }; 8912 } 8913 8914 Sizzle.isXML = function( elem ) { 8915 // documentElement is verified for cases where it doesn't yet exist 8916 // (such as loading iframes in IE - #4833) 8917 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; 8918 8919 return documentElement ? documentElement.nodeName !== "HTML" : false; 8920 }; 8921 8922 var posProcess = function( selector, context, seed ) { 8923 var match, 8924 tmpSet = [], 8925 later = "", 8926 root = context.nodeType ? [context] : context; 8927 8928 // Position selectors must be done after the filter 8929 // And so must :not(positional) so we move all PSEUDOs to the end 8930 while ( (match = Expr.match.PSEUDO.exec( selector )) ) { 8931 later += match[0]; 8932 selector = selector.replace( Expr.match.PSEUDO, "" ); 8933 } 8934 8935 selector = Expr.relative[selector] ? selector + "*" : selector; 8936 8937 for ( var i = 0, l = root.length; i < l; i++ ) { 8938 Sizzle( selector, root[i], tmpSet, seed ); 8939 } 8940 8941 return Sizzle.filter( later, tmpSet ); 8942 }; 8943 8944 // EXPOSE 8945 8946 window.tinymce.dom.Sizzle = Sizzle; 8947 8948 })(); 8949 8950 8951 (function(tinymce) { 8952 tinymce.dom.Element = function(id, settings) { 8953 var t = this, dom, el; 8954 8955 t.settings = settings = settings || {}; 8956 t.id = id; 8957 t.dom = dom = settings.dom || tinymce.DOM; 8958 8959 // Only IE leaks DOM references, this is a lot faster 8960 if (!tinymce.isIE) 8961 el = dom.get(t.id); 8962 8963 tinymce.each( 8964 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 8965 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 8966 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 8967 'isHidden,setHTML,get').split(/,/), function(k) { 8968 t[k] = function() { 8969 var a = [id], i; 8970 8971 for (i = 0; i < arguments.length; i++) 8972 a.push(arguments[i]); 8973 8974 a = dom[k].apply(dom, a); 8975 t.update(k); 8976 8977 return a; 8978 }; 8979 } 8980 ); 8981 8982 tinymce.extend(t, { 8983 on : function(n, f, s) { 8984 return tinymce.dom.Event.add(t.id, n, f, s); 8985 }, 8986 8987 getXY : function() { 8988 return { 8989 x : parseInt(t.getStyle('left')), 8990 y : parseInt(t.getStyle('top')) 8991 }; 8992 }, 8993 8994 getSize : function() { 8995 var n = dom.get(t.id); 8996 8997 return { 8998 w : parseInt(t.getStyle('width') || n.clientWidth), 8999 h : parseInt(t.getStyle('height') || n.clientHeight) 9000 }; 9001 }, 9002 9003 moveTo : function(x, y) { 9004 t.setStyles({left : x, top : y}); 9005 }, 9006 9007 moveBy : function(x, y) { 9008 var p = t.getXY(); 9009 9010 t.moveTo(p.x + x, p.y + y); 9011 }, 9012 9013 resizeTo : function(w, h) { 9014 t.setStyles({width : w, height : h}); 9015 }, 9016 9017 resizeBy : function(w, h) { 9018 var s = t.getSize(); 9019 9020 t.resizeTo(s.w + w, s.h + h); 9021 }, 9022 9023 update : function(k) { 9024 var b; 9025 9026 if (tinymce.isIE6 && settings.blocker) { 9027 k = k || ''; 9028 9029 // Ignore getters 9030 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) 9031 return; 9032 9033 // Remove blocker on remove 9034 if (k == 'remove') { 9035 dom.remove(t.blocker); 9036 return; 9037 } 9038 9039 if (!t.blocker) { 9040 t.blocker = dom.uniqueId(); 9041 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); 9042 dom.setStyle(b, 'opacity', 0); 9043 } else 9044 b = dom.get(t.blocker); 9045 9046 dom.setStyles(b, { 9047 left : t.getStyle('left', 1), 9048 top : t.getStyle('top', 1), 9049 width : t.getStyle('width', 1), 9050 height : t.getStyle('height', 1), 9051 display : t.getStyle('display', 1), 9052 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 9053 }); 9054 } 9055 } 9056 }); 9057 }; 9058 })(tinymce); 9059 9060 (function(tinymce) { 9061 function trimNl(s) { 9062 return s.replace(/[\n\r]+/g, ''); 9063 }; 9064 9065 // Shorten names 9066 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker; 9067 9068 tinymce.create('tinymce.dom.Selection', { 9069 Selection : function(dom, win, serializer, editor) { 9070 var t = this; 9071 9072 t.dom = dom; 9073 t.win = win; 9074 t.serializer = serializer; 9075 t.editor = editor; 9076 9077 // Add events 9078 each([ 9079 'onBeforeSetContent', 9080 9081 'onBeforeGetContent', 9082 9083 'onSetContent', 9084 9085 'onGetContent' 9086 ], function(e) { 9087 t[e] = new tinymce.util.Dispatcher(t); 9088 }); 9089 9090 // No W3C Range support 9091 if (!t.win.getSelection) 9092 t.tridentSel = new tinymce.dom.TridentSelection(t); 9093 9094 if (tinymce.isIE && dom.boxModel) 9095 this._fixIESelection(); 9096 9097 // Prevent leaks 9098 tinymce.addUnload(t.destroy, t); 9099 }, 9100 9101 setCursorLocation: function(node, offset) { 9102 var t = this; var r = t.dom.createRng(); 9103 r.setStart(node, offset); 9104 r.setEnd(node, offset); 9105 t.setRng(r); 9106 t.collapse(false); 9107 }, 9108 getContent : function(s) { 9109 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; 9110 9111 s = s || {}; 9112 wb = wa = ''; 9113 s.get = true; 9114 s.format = s.format || 'html'; 9115 s.forced_root_block = ''; 9116 t.onBeforeGetContent.dispatch(t, s); 9117 9118 if (s.format == 'text') 9119 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); 9120 9121 if (r.cloneContents) { 9122 n = r.cloneContents(); 9123 9124 if (n) 9125 e.appendChild(n); 9126 } else if (is(r.item) || is(r.htmlText)) { 9127 // IE will produce invalid markup if elements are present that 9128 // it doesn't understand like custom elements or HTML5 elements. 9129 // Adding a BR in front of the contents and then remoiving it seems to fix it though. 9130 e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText); 9131 e.removeChild(e.firstChild); 9132 } else 9133 e.innerHTML = r.toString(); 9134 9135 // Keep whitespace before and after 9136 if (/^\s/.test(e.innerHTML)) 9137 wb = ' '; 9138 9139 if (/\s+$/.test(e.innerHTML)) 9140 wa = ' '; 9141 9142 s.getInner = true; 9143 9144 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; 9145 t.onGetContent.dispatch(t, s); 9146 9147 return s.content; 9148 }, 9149 9150 setContent : function(content, args) { 9151 var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; 9152 9153 args = args || {format : 'html'}; 9154 args.set = true; 9155 content = args.content = content; 9156 9157 // Dispatch before set content event 9158 if (!args.no_events) 9159 self.onBeforeSetContent.dispatch(self, args); 9160 9161 content = args.content; 9162 9163 if (rng.insertNode) { 9164 // Make caret marker since insertNode places the caret in the beginning of text after insert 9165 content += '<span id="__caret">_</span>'; 9166 9167 // Delete and insert new node 9168 if (rng.startContainer == doc && rng.endContainer == doc) { 9169 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents 9170 doc.body.innerHTML = content; 9171 } else { 9172 rng.deleteContents(); 9173 9174 if (doc.body.childNodes.length === 0) { 9175 doc.body.innerHTML = content; 9176 } else { 9177 // createContextualFragment doesn't exists in IE 9 DOMRanges 9178 if (rng.createContextualFragment) { 9179 rng.insertNode(rng.createContextualFragment(content)); 9180 } else { 9181 // Fake createContextualFragment call in IE 9 9182 frag = doc.createDocumentFragment(); 9183 temp = doc.createElement('div'); 9184 9185 frag.appendChild(temp); 9186 temp.outerHTML = content; 9187 9188 rng.insertNode(frag); 9189 } 9190 } 9191 } 9192 9193 // Move to caret marker 9194 caretNode = self.dom.get('__caret'); 9195 9196 // Make sure we wrap it compleatly, Opera fails with a simple select call 9197 rng = doc.createRange(); 9198 rng.setStartBefore(caretNode); 9199 rng.setEndBefore(caretNode); 9200 self.setRng(rng); 9201 9202 // Remove the caret position 9203 self.dom.remove('__caret'); 9204 9205 try { 9206 self.setRng(rng); 9207 } catch (ex) { 9208 // Might fail on Opera for some odd reason 9209 } 9210 } else { 9211 if (rng.item) { 9212 // Delete content and get caret text selection 9213 doc.execCommand('Delete', false, null); 9214 rng = self.getRng(); 9215 } 9216 9217 // Explorer removes spaces from the beginning of pasted contents 9218 if (/^\s+/.test(content)) { 9219 rng.pasteHTML('<span id="__mce_tmp">_</span>' + content); 9220 self.dom.remove('__mce_tmp'); 9221 } else 9222 rng.pasteHTML(content); 9223 } 9224 9225 // Dispatch set content event 9226 if (!args.no_events) 9227 self.onSetContent.dispatch(self, args); 9228 }, 9229 9230 getStart : function() { 9231 var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node; 9232 9233 if (rng.duplicate || rng.item) { 9234 // Control selection, return first item 9235 if (rng.item) 9236 return rng.item(0); 9237 9238 // Get start element 9239 checkRng = rng.duplicate(); 9240 checkRng.collapse(1); 9241 startElement = checkRng.parentElement(); 9242 if (startElement.ownerDocument !== self.dom.doc) { 9243 startElement = self.dom.getRoot(); 9244 } 9245 9246 // Check if range parent is inside the start element, then return the inner parent element 9247 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element 9248 parentElement = node = rng.parentElement(); 9249 while (node = node.parentNode) { 9250 if (node == startElement) { 9251 startElement = parentElement; 9252 break; 9253 } 9254 } 9255 9256 return startElement; 9257 } else { 9258 startElement = rng.startContainer; 9259 9260 if (startElement.nodeType == 1 && startElement.hasChildNodes()) 9261 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; 9262 9263 if (startElement && startElement.nodeType == 3) 9264 return startElement.parentNode; 9265 9266 return startElement; 9267 } 9268 }, 9269 9270 getEnd : function() { 9271 var self = this, rng = self.getRng(), endElement, endOffset; 9272 9273 if (rng.duplicate || rng.item) { 9274 if (rng.item) 9275 return rng.item(0); 9276 9277 rng = rng.duplicate(); 9278 rng.collapse(0); 9279 endElement = rng.parentElement(); 9280 if (endElement.ownerDocument !== self.dom.doc) { 9281 endElement = self.dom.getRoot(); 9282 } 9283 9284 if (endElement && endElement.nodeName == 'BODY') 9285 return endElement.lastChild || endElement; 9286 9287 return endElement; 9288 } else { 9289 endElement = rng.endContainer; 9290 endOffset = rng.endOffset; 9291 9292 if (endElement.nodeType == 1 && endElement.hasChildNodes()) 9293 endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset]; 9294 9295 if (endElement && endElement.nodeType == 3) 9296 return endElement.parentNode; 9297 9298 return endElement; 9299 } 9300 }, 9301 9302 getBookmark : function(type, normalized) { 9303 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; 9304 9305 function findIndex(name, element) { 9306 var index = 0; 9307 9308 each(dom.select(name), function(node, i) { 9309 if (node == element) 9310 index = i; 9311 }); 9312 9313 return index; 9314 }; 9315 9316 function normalizeTableCellSelection(rng) { 9317 function moveEndPoint(start) { 9318 var container, offset, childNodes, prefix = start ? 'start' : 'end'; 9319 9320 container = rng[prefix + 'Container']; 9321 offset = rng[prefix + 'Offset']; 9322 9323 if (container.nodeType == 1 && container.nodeName == "TR") { 9324 childNodes = container.childNodes; 9325 container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]; 9326 if (container) { 9327 offset = start ? 0 : container.childNodes.length; 9328 rng['set' + (start ? 'Start' : 'End')](container, offset); 9329 } 9330 } 9331 }; 9332 9333 moveEndPoint(true); 9334 moveEndPoint(); 9335 9336 return rng; 9337 }; 9338 9339 function getLocation() { 9340 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; 9341 9342 function getPoint(rng, start) { 9343 var container = rng[start ? 'startContainer' : 'endContainer'], 9344 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; 9345 9346 if (container.nodeType == 3) { 9347 if (normalized) { 9348 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) 9349 offset += node.nodeValue.length; 9350 } 9351 9352 point.push(offset); 9353 } else { 9354 childNodes = container.childNodes; 9355 9356 if (offset >= childNodes.length && childNodes.length) { 9357 after = 1; 9358 offset = Math.max(0, childNodes.length - 1); 9359 } 9360 9361 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); 9362 } 9363 9364 for (; container && container != root; container = container.parentNode) 9365 point.push(t.dom.nodeIndex(container, normalized)); 9366 9367 return point; 9368 }; 9369 9370 bookmark.start = getPoint(rng, true); 9371 9372 if (!t.isCollapsed()) 9373 bookmark.end = getPoint(rng); 9374 9375 return bookmark; 9376 }; 9377 9378 if (type == 2) { 9379 if (t.tridentSel) 9380 return t.tridentSel.getBookmark(type); 9381 9382 return getLocation(); 9383 } 9384 9385 // Handle simple range 9386 if (type) 9387 return {rng : t.getRng()}; 9388 9389 rng = t.getRng(); 9390 id = dom.uniqueId(); 9391 collapsed = tinyMCE.activeEditor.selection.isCollapsed(); 9392 styles = 'overflow:hidden;line-height:0px'; 9393 9394 // Explorer method 9395 if (rng.duplicate || rng.item) { 9396 // Text selection 9397 if (!rng.item) { 9398 rng2 = rng.duplicate(); 9399 9400 try { 9401 // Insert start marker 9402 rng.collapse(); 9403 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>'); 9404 9405 // Insert end marker 9406 if (!collapsed) { 9407 rng2.collapse(false); 9408 9409 // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p> 9410 rng.moveToElementText(rng2.parentElement()); 9411 if (rng.compareEndPoints('StartToEnd', rng2) === 0) 9412 rng2.move('character', -1); 9413 9414 rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>'); 9415 } 9416 } catch (ex) { 9417 // IE might throw unspecified error so lets ignore it 9418 return null; 9419 } 9420 } else { 9421 // Control selection 9422 element = rng.item(0); 9423 name = element.nodeName; 9424 9425 return {name : name, index : findIndex(name, element)}; 9426 } 9427 } else { 9428 element = t.getNode(); 9429 name = element.nodeName; 9430 if (name == 'IMG') 9431 return {name : name, index : findIndex(name, element)}; 9432 9433 // W3C method 9434 rng2 = normalizeTableCellSelection(rng.cloneRange()); 9435 9436 // Insert end marker 9437 if (!collapsed) { 9438 rng2.collapse(false); 9439 rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); 9440 } 9441 9442 rng = normalizeTableCellSelection(rng); 9443 rng.collapse(true); 9444 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); 9445 } 9446 9447 t.moveToBookmark({id : id, keep : 1}); 9448 9449 return {id : id}; 9450 }, 9451 9452 moveToBookmark : function(bookmark) { 9453 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset; 9454 9455 function setEndPoint(start) { 9456 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; 9457 9458 if (point) { 9459 offset = point[0]; 9460 9461 // Find container node 9462 for (node = root, i = point.length - 1; i >= 1; i--) { 9463 children = node.childNodes; 9464 9465 if (point[i] > children.length - 1) 9466 return; 9467 9468 node = children[point[i]]; 9469 } 9470 9471 // Move text offset to best suitable location 9472 if (node.nodeType === 3) 9473 offset = Math.min(point[0], node.nodeValue.length); 9474 9475 // Move element offset to best suitable location 9476 if (node.nodeType === 1) 9477 offset = Math.min(point[0], node.childNodes.length); 9478 9479 // Set offset within container node 9480 if (start) 9481 rng.setStart(node, offset); 9482 else 9483 rng.setEnd(node, offset); 9484 } 9485 9486 return true; 9487 }; 9488 9489 function restoreEndPoint(suffix) { 9490 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; 9491 9492 if (marker) { 9493 node = marker.parentNode; 9494 9495 if (suffix == 'start') { 9496 if (!keep) { 9497 idx = dom.nodeIndex(marker); 9498 } else { 9499 node = marker.firstChild; 9500 idx = 1; 9501 } 9502 9503 startContainer = endContainer = node; 9504 startOffset = endOffset = idx; 9505 } else { 9506 if (!keep) { 9507 idx = dom.nodeIndex(marker); 9508 } else { 9509 node = marker.firstChild; 9510 idx = 1; 9511 } 9512 9513 endContainer = node; 9514 endOffset = idx; 9515 } 9516 9517 if (!keep) { 9518 prev = marker.previousSibling; 9519 next = marker.nextSibling; 9520 9521 // Remove all marker text nodes 9522 each(tinymce.grep(marker.childNodes), function(node) { 9523 if (node.nodeType == 3) 9524 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); 9525 }); 9526 9527 // Remove marker but keep children if for example contents where inserted into the marker 9528 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature 9529 while (marker = dom.get(bookmark.id + '_' + suffix)) 9530 dom.remove(marker, 1); 9531 9532 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node 9533 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact 9534 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { 9535 idx = prev.nodeValue.length; 9536 prev.appendData(next.nodeValue); 9537 dom.remove(next); 9538 9539 if (suffix == 'start') { 9540 startContainer = endContainer = prev; 9541 startOffset = endOffset = idx; 9542 } else { 9543 endContainer = prev; 9544 endOffset = idx; 9545 } 9546 } 9547 } 9548 } 9549 }; 9550 9551 function addBogus(node) { 9552 // Adds a bogus BR element for empty block elements 9553 if (dom.isBlock(node) && !node.innerHTML && !isIE) 9554 node.innerHTML = '<br data-mce-bogus="1" />'; 9555 9556 return node; 9557 }; 9558 9559 if (bookmark) { 9560 if (bookmark.start) { 9561 rng = dom.createRng(); 9562 root = dom.getRoot(); 9563 9564 if (t.tridentSel) 9565 return t.tridentSel.moveToBookmark(bookmark); 9566 9567 if (setEndPoint(true) && setEndPoint()) { 9568 t.setRng(rng); 9569 } 9570 } else if (bookmark.id) { 9571 // Restore start/end points 9572 restoreEndPoint('start'); 9573 restoreEndPoint('end'); 9574 9575 if (startContainer) { 9576 rng = dom.createRng(); 9577 rng.setStart(addBogus(startContainer), startOffset); 9578 rng.setEnd(addBogus(endContainer), endOffset); 9579 t.setRng(rng); 9580 } 9581 } else if (bookmark.name) { 9582 t.select(dom.select(bookmark.name)[bookmark.index]); 9583 } else if (bookmark.rng) 9584 t.setRng(bookmark.rng); 9585 } 9586 }, 9587 9588 select : function(node, content) { 9589 var t = this, dom = t.dom, rng = dom.createRng(), idx; 9590 9591 function setPoint(node, start) { 9592 var walker = new TreeWalker(node, node); 9593 9594 do { 9595 // Text node 9596 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) { 9597 if (start) 9598 rng.setStart(node, 0); 9599 else 9600 rng.setEnd(node, node.nodeValue.length); 9601 9602 return; 9603 } 9604 9605 // BR element 9606 if (node.nodeName == 'BR') { 9607 if (start) 9608 rng.setStartBefore(node); 9609 else 9610 rng.setEndBefore(node); 9611 9612 return; 9613 } 9614 } while (node = (start ? walker.next() : walker.prev())); 9615 }; 9616 9617 if (node) { 9618 idx = dom.nodeIndex(node); 9619 rng.setStart(node.parentNode, idx); 9620 rng.setEnd(node.parentNode, idx + 1); 9621 9622 // Find first/last text node or BR element 9623 if (content) { 9624 setPoint(node, 1); 9625 setPoint(node); 9626 } 9627 9628 t.setRng(rng); 9629 } 9630 9631 return node; 9632 }, 9633 9634 isCollapsed : function() { 9635 var t = this, r = t.getRng(), s = t.getSel(); 9636 9637 if (!r || r.item) 9638 return false; 9639 9640 if (r.compareEndPoints) 9641 return r.compareEndPoints('StartToEnd', r) === 0; 9642 9643 return !s || r.collapsed; 9644 }, 9645 9646 collapse : function(to_start) { 9647 var self = this, rng = self.getRng(), node; 9648 9649 // Control range on IE 9650 if (rng.item) { 9651 node = rng.item(0); 9652 rng = self.win.document.body.createTextRange(); 9653 rng.moveToElementText(node); 9654 } 9655 9656 rng.collapse(!!to_start); 9657 self.setRng(rng); 9658 }, 9659 9660 getSel : function() { 9661 var t = this, w = this.win; 9662 9663 return w.getSelection ? w.getSelection() : w.document.selection; 9664 }, 9665 9666 getRng : function(w3c) { 9667 var self = this, selection, rng, elm, doc = self.win.document; 9668 9669 // Found tridentSel object then we need to use that one 9670 if (w3c && self.tridentSel) { 9671 return self.tridentSel.getRangeAt(0); 9672 } 9673 9674 try { 9675 if (selection = self.getSel()) { 9676 rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange()); 9677 } 9678 } catch (ex) { 9679 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe 9680 } 9681 9682 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet 9683 if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) { 9684 elm = doc.selection.createRange().item(0); 9685 rng = doc.createRange(); 9686 rng.setStartBefore(elm); 9687 rng.setEndAfter(elm); 9688 } 9689 9690 // No range found then create an empty one 9691 // This can occur when the editor is placed in a hidden container element on Gecko 9692 // Or on IE when there was an exception 9693 if (!rng) { 9694 rng = doc.createRange ? doc.createRange() : doc.body.createTextRange(); 9695 } 9696 9697 // If range is at start of document then move it to start of body 9698 if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) { 9699 elm = self.dom.getRoot(); 9700 rng.setStart(elm, 0); 9701 rng.setEnd(elm, 0); 9702 } 9703 9704 if (self.selectedRange && self.explicitRange) { 9705 if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) { 9706 // Safari, Opera and Chrome only ever select text which causes the range to change. 9707 // This lets us use the originally set range if the selection hasn't been changed by the user. 9708 rng = self.explicitRange; 9709 } else { 9710 self.selectedRange = null; 9711 self.explicitRange = null; 9712 } 9713 } 9714 9715 return rng; 9716 }, 9717 9718 setRng : function(r, forward) { 9719 var s, t = this; 9720 9721 if (!t.tridentSel) { 9722 s = t.getSel(); 9723 9724 if (s) { 9725 t.explicitRange = r; 9726 9727 try { 9728 s.removeAllRanges(); 9729 } catch (ex) { 9730 // IE9 might throw errors here don't know why 9731 } 9732 9733 s.addRange(r); 9734 9735 // Forward is set to false and we have an extend function 9736 if (forward === false && s.extend) { 9737 s.collapse(r.endContainer, r.endOffset); 9738 s.extend(r.startContainer, r.startOffset); 9739 } 9740 9741 // adding range isn't always successful so we need to check range count otherwise an exception can occur 9742 t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null; 9743 } 9744 } else { 9745 // Is W3C Range 9746 if (r.cloneRange) { 9747 try { 9748 t.tridentSel.addRange(r); 9749 return; 9750 } catch (ex) { 9751 //IE9 throws an error here if called before selection is placed in the editor 9752 } 9753 } 9754 9755 // Is IE specific range 9756 try { 9757 r.select(); 9758 } catch (ex) { 9759 // Needed for some odd IE bug #1843306 9760 } 9761 } 9762 }, 9763 9764 setNode : function(n) { 9765 var t = this; 9766 9767 t.setContent(t.dom.getOuterHTML(n)); 9768 9769 return n; 9770 }, 9771 9772 getNode : function() { 9773 var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer; 9774 9775 function skipEmptyTextNodes(n, forwards) { 9776 var orig = n; 9777 while (n && n.nodeType === 3 && n.length === 0) { 9778 n = forwards ? n.nextSibling : n.previousSibling; 9779 } 9780 return n || orig; 9781 }; 9782 9783 // Range maybe lost after the editor is made visible again 9784 if (!rng) 9785 return t.dom.getRoot(); 9786 9787 if (rng.setStart) { 9788 elm = rng.commonAncestorContainer; 9789 9790 // Handle selection a image or other control like element such as anchors 9791 if (!rng.collapsed) { 9792 if (rng.startContainer == rng.endContainer) { 9793 if (rng.endOffset - rng.startOffset < 2) { 9794 if (rng.startContainer.hasChildNodes()) 9795 elm = rng.startContainer.childNodes[rng.startOffset]; 9796 } 9797 } 9798 9799 // If the anchor node is a element instead of a text node then return this element 9800 //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 9801 // return sel.anchorNode.childNodes[sel.anchorOffset]; 9802 9803 // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. 9804 // This happens when you double click an underlined word in FireFox. 9805 if (start.nodeType === 3 && end.nodeType === 3) { 9806 if (start.length === rng.startOffset) { 9807 start = skipEmptyTextNodes(start.nextSibling, true); 9808 } else { 9809 start = start.parentNode; 9810 } 9811 if (rng.endOffset === 0) { 9812 end = skipEmptyTextNodes(end.previousSibling, false); 9813 } else { 9814 end = end.parentNode; 9815 } 9816 9817 if (start && start === end) 9818 return start; 9819 } 9820 } 9821 9822 if (elm && elm.nodeType == 3) 9823 return elm.parentNode; 9824 9825 return elm; 9826 } 9827 9828 return rng.item ? rng.item(0) : rng.parentElement(); 9829 }, 9830 9831 getSelectedBlocks : function(st, en) { 9832 var t = this, dom = t.dom, sb, eb, n, bl = []; 9833 9834 sb = dom.getParent(st || t.getStart(), dom.isBlock); 9835 eb = dom.getParent(en || t.getEnd(), dom.isBlock); 9836 9837 if (sb) 9838 bl.push(sb); 9839 9840 if (sb && eb && sb != eb) { 9841 n = sb; 9842 9843 var walker = new TreeWalker(sb, dom.getRoot()); 9844 while ((n = walker.next()) && n != eb) { 9845 if (dom.isBlock(n)) 9846 bl.push(n); 9847 } 9848 } 9849 9850 if (eb && sb != eb) 9851 bl.push(eb); 9852 9853 return bl; 9854 }, 9855 9856 isForward: function(){ 9857 var dom = this.dom, sel = this.getSel(), anchorRange, focusRange; 9858 9859 // No support for selection direction then always return true 9860 if (!sel || sel.anchorNode == null || sel.focusNode == null) { 9861 return true; 9862 } 9863 9864 anchorRange = dom.createRng(); 9865 anchorRange.setStart(sel.anchorNode, sel.anchorOffset); 9866 anchorRange.collapse(true); 9867 9868 focusRange = dom.createRng(); 9869 focusRange.setStart(sel.focusNode, sel.focusOffset); 9870 focusRange.collapse(true); 9871 9872 return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0; 9873 }, 9874 9875 normalize : function() { 9876 var self = this, rng, normalized, collapsed, node, sibling; 9877 9878 function normalizeEndPoint(start) { 9879 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName; 9880 9881 function hasBrBeforeAfter(node, left) { 9882 var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body); 9883 9884 while (node = walker[left ? 'prev' : 'next']()) { 9885 if (node.nodeName === "BR") { 9886 return true; 9887 } 9888 } 9889 }; 9890 9891 // Walks the dom left/right to find a suitable text node to move the endpoint into 9892 // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG 9893 function findTextNodeRelative(left, startNode) { 9894 var walker, lastInlineElement; 9895 9896 startNode = startNode || container; 9897 walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body); 9898 9899 // Walk left until we hit a text node we can move to or a block/br/img 9900 while (node = walker[left ? 'prev' : 'next']()) { 9901 // Found text node that has a length 9902 if (node.nodeType === 3 && node.nodeValue.length > 0) { 9903 container = node; 9904 offset = left ? node.nodeValue.length : 0; 9905 normalized = true; 9906 return; 9907 } 9908 9909 // Break if we find a block or a BR/IMG/INPUT etc 9910 if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 9911 return; 9912 } 9913 9914 lastInlineElement = node; 9915 } 9916 9917 // Only fetch the last inline element when in caret mode for now 9918 if (collapsed && lastInlineElement) { 9919 container = lastInlineElement; 9920 normalized = true; 9921 offset = 0; 9922 } 9923 }; 9924 9925 container = rng[(start ? 'start' : 'end') + 'Container']; 9926 offset = rng[(start ? 'start' : 'end') + 'Offset']; 9927 nonEmptyElementsMap = dom.schema.getNonEmptyElements(); 9928 9929 // If the container is a document move it to the body element 9930 if (container.nodeType === 9) { 9931 container = dom.getRoot(); 9932 offset = 0; 9933 } 9934 9935 // If the container is body try move it into the closest text node or position 9936 if (container === body) { 9937 // If start is before/after a image, table etc 9938 if (start) { 9939 node = container.childNodes[offset > 0 ? offset - 1 : 0]; 9940 if (node) { 9941 nodeName = node.nodeName.toLowerCase(); 9942 if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") { 9943 return; 9944 } 9945 } 9946 } 9947 9948 // Resolve the index 9949 if (container.hasChildNodes()) { 9950 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)]; 9951 offset = 0; 9952 9953 // Don't walk into elements that doesn't have any child nodes like a IMG 9954 if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) { 9955 // Walk the DOM to find a text node to place the caret at or a BR 9956 node = container; 9957 walker = new TreeWalker(container, body); 9958 9959 do { 9960 // Found a text node use that position 9961 if (node.nodeType === 3 && node.nodeValue.length > 0) { 9962 offset = start ? 0 : node.nodeValue.length; 9963 container = node; 9964 normalized = true; 9965 break; 9966 } 9967 9968 // Found a BR/IMG element that we can place the caret before 9969 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 9970 offset = dom.nodeIndex(node); 9971 container = node.parentNode; 9972 9973 // Put caret after image when moving the end point 9974 if (node.nodeName == "IMG" && !start) { 9975 offset++; 9976 } 9977 9978 normalized = true; 9979 break; 9980 } 9981 } while (node = (start ? walker.next() : walker.prev())); 9982 } 9983 } 9984 } 9985 9986 // Lean the caret to the left if possible 9987 if (collapsed) { 9988 // So this: <b>x</b><i>|x</i> 9989 // Becomes: <b>x|</b><i>x</i> 9990 // Seems that only gecko has issues with this 9991 if (container.nodeType === 3 && offset === 0) { 9992 findTextNodeRelative(true); 9993 } 9994 9995 // Lean left into empty inline elements when the caret is before a BR 9996 // So this: <i><b></b><i>|<br></i> 9997 // Becomes: <i><b>|</b><i><br></i> 9998 // Seems that only gecko has issues with this 9999 if (container.nodeType === 1) { 10000 node = container.childNodes[offset]; 10001 if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) { 10002 findTextNodeRelative(true, container.childNodes[offset]); 10003 } 10004 } 10005 } 10006 10007 // Lean the start of the selection right if possible 10008 // So this: x[<b>x]</b> 10009 // Becomes: x<b>[x]</b> 10010 if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) { 10011 findTextNodeRelative(false); 10012 } 10013 10014 // Set endpoint if it was normalized 10015 if (normalized) 10016 rng['set' + (start ? 'Start' : 'End')](container, offset); 10017 }; 10018 10019 // Normalize only on non IE browsers for now 10020 if (tinymce.isIE) 10021 return; 10022 10023 rng = self.getRng(); 10024 collapsed = rng.collapsed; 10025 10026 // Normalize the end points 10027 normalizeEndPoint(true); 10028 10029 if (!collapsed) 10030 normalizeEndPoint(); 10031 10032 // Set the selection if it was normalized 10033 if (normalized) { 10034 // If it was collapsed then make sure it still is 10035 if (collapsed) { 10036 rng.collapse(true); 10037 } 10038 10039 //console.log(self.dom.dumpRng(rng)); 10040 self.setRng(rng, self.isForward()); 10041 } 10042 }, 10043 10044 selectorChanged: function(selector, callback) { 10045 var self = this, currentSelectors; 10046 10047 if (!self.selectorChangedData) { 10048 self.selectorChangedData = {}; 10049 currentSelectors = {}; 10050 10051 self.editor.onNodeChange.addToTop(function(ed, cm, node) { 10052 var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {}; 10053 10054 // Check for new matching selectors 10055 each(self.selectorChangedData, function(callbacks, selector) { 10056 each(parents, function(node) { 10057 if (dom.is(node, selector)) { 10058 if (!currentSelectors[selector]) { 10059 // Execute callbacks 10060 each(callbacks, function(callback) { 10061 callback(true, {node: node, selector: selector, parents: parents}); 10062 }); 10063 10064 currentSelectors[selector] = callbacks; 10065 } 10066 10067 matchedSelectors[selector] = callbacks; 10068 return false; 10069 } 10070 }); 10071 }); 10072 10073 // Check if current selectors still match 10074 each(currentSelectors, function(callbacks, selector) { 10075 if (!matchedSelectors[selector]) { 10076 delete currentSelectors[selector]; 10077 10078 each(callbacks, function(callback) { 10079 callback(false, {node: node, selector: selector, parents: parents}); 10080 }); 10081 } 10082 }); 10083 }); 10084 } 10085 10086 // Add selector listeners 10087 if (!self.selectorChangedData[selector]) { 10088 self.selectorChangedData[selector] = []; 10089 } 10090 10091 self.selectorChangedData[selector].push(callback); 10092 10093 return self; 10094 }, 10095 10096 destroy : function(manual) { 10097 var self = this; 10098 10099 self.win = null; 10100 10101 // Manual destroy then remove unload handler 10102 if (!manual) 10103 tinymce.removeUnload(self.destroy); 10104 }, 10105 10106 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode 10107 _fixIESelection : function() { 10108 var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm; 10109 10110 // Return range from point or null if it failed 10111 function rngFromPoint(x, y) { 10112 var rng = body.createTextRange(); 10113 10114 try { 10115 rng.moveToPoint(x, y); 10116 } catch (ex) { 10117 // IE sometimes throws and exception, so lets just ignore it 10118 rng = null; 10119 } 10120 10121 return rng; 10122 }; 10123 10124 // Fires while the selection is changing 10125 function selectionChange(e) { 10126 var pointRng; 10127 10128 // Check if the button is down or not 10129 if (e.button) { 10130 // Create range from mouse position 10131 pointRng = rngFromPoint(e.x, e.y); 10132 10133 if (pointRng) { 10134 // Check if pointRange is before/after selection then change the endPoint 10135 if (pointRng.compareEndPoints('StartToStart', startRng) > 0) 10136 pointRng.setEndPoint('StartToStart', startRng); 10137 else 10138 pointRng.setEndPoint('EndToEnd', startRng); 10139 10140 pointRng.select(); 10141 } 10142 } else 10143 endSelection(); 10144 } 10145 10146 // Removes listeners 10147 function endSelection() { 10148 var rng = doc.selection.createRange(); 10149 10150 // If the range is collapsed then use the last start range 10151 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) 10152 startRng.select(); 10153 10154 dom.unbind(doc, 'mouseup', endSelection); 10155 dom.unbind(doc, 'mousemove', selectionChange); 10156 startRng = started = 0; 10157 }; 10158 10159 // Make HTML element unselectable since we are going to handle selection by hand 10160 doc.documentElement.unselectable = true; 10161 10162 // Detect when user selects outside BODY 10163 dom.bind(doc, ['mousedown', 'contextmenu'], function(e) { 10164 if (e.target.nodeName === 'HTML') { 10165 if (started) 10166 endSelection(); 10167 10168 // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML 10169 htmlElm = doc.documentElement; 10170 if (htmlElm.scrollHeight > htmlElm.clientHeight) 10171 return; 10172 10173 started = 1; 10174 // Setup start position 10175 startRng = rngFromPoint(e.x, e.y); 10176 if (startRng) { 10177 // Listen for selection change events 10178 dom.bind(doc, 'mouseup', endSelection); 10179 dom.bind(doc, 'mousemove', selectionChange); 10180 10181 dom.win.focus(); 10182 startRng.select(); 10183 } 10184 } 10185 }); 10186 } 10187 }); 10188 })(tinymce); 10189 10190 (function(tinymce) { 10191 tinymce.dom.Serializer = function(settings, dom, schema) { 10192 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser; 10193 10194 // Support the old apply_source_formatting option 10195 if (!settings.apply_source_formatting) 10196 settings.indent = false; 10197 10198 // Default DOM and Schema if they are undefined 10199 dom = dom || tinymce.DOM; 10200 schema = schema || new tinymce.html.Schema(settings); 10201 settings.entity_encoding = settings.entity_encoding || 'named'; 10202 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true; 10203 10204 onPreProcess = new tinymce.util.Dispatcher(self); 10205 10206 onPostProcess = new tinymce.util.Dispatcher(self); 10207 10208 htmlParser = new tinymce.html.DomParser(settings, schema); 10209 10210 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed 10211 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { 10212 var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; 10213 10214 while (i--) { 10215 node = nodes[i]; 10216 10217 value = node.attributes.map[internalName]; 10218 if (value !== undef) { 10219 // Set external name to internal value and remove internal 10220 node.attr(name, value.length > 0 ? value : null); 10221 node.attr(internalName, null); 10222 } else { 10223 // No internal attribute found then convert the value we have in the DOM 10224 value = node.attributes.map[name]; 10225 10226 if (name === "style") 10227 value = dom.serializeStyle(dom.parseStyle(value), node.name); 10228 else if (urlConverter) 10229 value = urlConverter.call(urlConverterScope, value, name, node.name); 10230 10231 node.attr(name, value.length > 0 ? value : null); 10232 } 10233 } 10234 }); 10235 10236 // Remove internal classes mceItem<..> or mceSelected 10237 htmlParser.addAttributeFilter('class', function(nodes, name) { 10238 var i = nodes.length, node, value; 10239 10240 while (i--) { 10241 node = nodes[i]; 10242 value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, ''); 10243 node.attr('class', value.length > 0 ? value : null); 10244 } 10245 }); 10246 10247 // Remove bookmark elements 10248 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { 10249 var i = nodes.length, node; 10250 10251 while (i--) { 10252 node = nodes[i]; 10253 10254 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) 10255 node.remove(); 10256 } 10257 }); 10258 10259 // Remove expando attributes 10260 htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) { 10261 var i = nodes.length; 10262 10263 while (i--) { 10264 nodes[i].attr(name, null); 10265 } 10266 }); 10267 10268 // Force script into CDATA sections and remove the mce- prefix also add comments around styles 10269 htmlParser.addNodeFilter('script,style', function(nodes, name) { 10270 var i = nodes.length, node, value; 10271 10272 function trim(value) { 10273 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n') 10274 .replace(/^[\r\n]*|[\r\n]*$/g, '') 10275 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '') 10276 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, ''); 10277 }; 10278 10279 while (i--) { 10280 node = nodes[i]; 10281 value = node.firstChild ? node.firstChild.value : ''; 10282 10283 if (name === "script") { 10284 // Remove mce- prefix from script elements 10285 node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, '')); 10286 10287 if (value.length > 0) 10288 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>'; 10289 } else { 10290 if (value.length > 0) 10291 node.firstChild.value = '<!--\n' + trim(value) + '\n-->'; 10292 } 10293 } 10294 }); 10295 10296 // Convert comments to cdata and handle protected comments 10297 htmlParser.addNodeFilter('#comment', function(nodes, name) { 10298 var i = nodes.length, node; 10299 10300 while (i--) { 10301 node = nodes[i]; 10302 10303 if (node.value.indexOf('[CDATA[') === 0) { 10304 node.name = '#cdata'; 10305 node.type = 4; 10306 node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); 10307 } else if (node.value.indexOf('mce:protected ') === 0) { 10308 node.name = "#text"; 10309 node.type = 3; 10310 node.raw = true; 10311 node.value = unescape(node.value).substr(14); 10312 } 10313 } 10314 }); 10315 10316 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { 10317 var i = nodes.length, node; 10318 10319 while (i--) { 10320 node = nodes[i]; 10321 if (node.type === 7) 10322 node.remove(); 10323 else if (node.type === 1) { 10324 if (name === "input" && !("type" in node.attributes.map)) 10325 node.attr('type', 'text'); 10326 } 10327 } 10328 }); 10329 10330 // Fix list elements, TODO: Replace this later 10331 if (settings.fix_list_elements) { 10332 htmlParser.addNodeFilter('ul,ol', function(nodes, name) { 10333 var i = nodes.length, node, parentNode; 10334 10335 while (i--) { 10336 node = nodes[i]; 10337 parentNode = node.parent; 10338 10339 if (parentNode.name === 'ul' || parentNode.name === 'ol') { 10340 if (node.prev && node.prev.name === 'li') { 10341 node.prev.append(node); 10342 } 10343 } 10344 } 10345 }); 10346 } 10347 10348 // Remove internal data attributes 10349 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) { 10350 var i = nodes.length; 10351 10352 while (i--) { 10353 nodes[i].attr(name, null); 10354 } 10355 }); 10356 10357 // Return public methods 10358 return { 10359 schema : schema, 10360 10361 addNodeFilter : htmlParser.addNodeFilter, 10362 10363 addAttributeFilter : htmlParser.addAttributeFilter, 10364 10365 onPreProcess : onPreProcess, 10366 10367 onPostProcess : onPostProcess, 10368 10369 serialize : function(node, args) { 10370 var impl, doc, oldDoc, htmlSerializer, content; 10371 10372 // Explorer won't clone contents of script and style and the 10373 // selected index of select elements are cleared on a clone operation. 10374 if (isIE && dom.select('script,style,select,map').length > 0) { 10375 content = node.innerHTML; 10376 node = node.cloneNode(false); 10377 dom.setHTML(node, content); 10378 } else 10379 node = node.cloneNode(true); 10380 10381 // Nodes needs to be attached to something in WebKit/Opera 10382 // Older builds of Opera crashes if you attach the node to an document created dynamically 10383 // and since we can't feature detect a crash we need to sniff the acutal build number 10384 // This fix will make DOM ranges and make Sizzle happy! 10385 impl = node.ownerDocument.implementation; 10386 if (impl.createHTMLDocument) { 10387 // Create an empty HTML document 10388 doc = impl.createHTMLDocument(""); 10389 10390 // Add the element or it's children if it's a body element to the new document 10391 each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { 10392 doc.body.appendChild(doc.importNode(node, true)); 10393 }); 10394 10395 // Grab first child or body element for serialization 10396 if (node.nodeName != 'BODY') 10397 node = doc.body.firstChild; 10398 else 10399 node = doc.body; 10400 10401 // set the new document in DOMUtils so createElement etc works 10402 oldDoc = dom.doc; 10403 dom.doc = doc; 10404 } 10405 10406 args = args || {}; 10407 args.format = args.format || 'html'; 10408 10409 // Pre process 10410 if (!args.no_events) { 10411 args.node = node; 10412 onPreProcess.dispatch(self, args); 10413 } 10414 10415 // Setup serializer 10416 htmlSerializer = new tinymce.html.Serializer(settings, schema); 10417 10418 // Parse and serialize HTML 10419 args.content = htmlSerializer.serialize( 10420 htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args) 10421 ); 10422 10423 // Replace all BOM characters for now until we can find a better solution 10424 if (!args.cleanup) 10425 args.content = args.content.replace(/\uFEFF|\u200B/g, ''); 10426 10427 // Post process 10428 if (!args.no_events) 10429 onPostProcess.dispatch(self, args); 10430 10431 // Restore the old document if it was changed 10432 if (oldDoc) 10433 dom.doc = oldDoc; 10434 10435 args.node = null; 10436 10437 return args.content; 10438 }, 10439 10440 addRules : function(rules) { 10441 schema.addValidElements(rules); 10442 }, 10443 10444 setRules : function(rules) { 10445 schema.setValidElements(rules); 10446 } 10447 }; 10448 }; 10449 })(tinymce); 10450 (function(tinymce) { 10451 tinymce.dom.ScriptLoader = function(settings) { 10452 var QUEUED = 0, 10453 LOADING = 1, 10454 LOADED = 2, 10455 states = {}, 10456 queue = [], 10457 scriptLoadedCallbacks = {}, 10458 queueLoadedCallbacks = [], 10459 loading = 0, 10460 undef; 10461 10462 function loadScript(url, callback) { 10463 var t = this, dom = tinymce.DOM, elm, uri, loc, id; 10464 10465 // Execute callback when script is loaded 10466 function done() { 10467 dom.remove(id); 10468 10469 if (elm) 10470 elm.onreadystatechange = elm.onload = elm = null; 10471 10472 callback(); 10473 }; 10474 10475 function error() { 10476 // Report the error so it's easier for people to spot loading errors 10477 if (typeof(console) !== "undefined" && console.log) 10478 console.log("Failed to load: " + url); 10479 10480 // We can't mark it as done if there is a load error since 10481 // A) We don't want to produce 404 errors on the server and 10482 // B) the onerror event won't fire on all browsers. 10483 // done(); 10484 }; 10485 10486 id = dom.uniqueId(); 10487 10488 if (tinymce.isIE6) { 10489 uri = new tinymce.util.URI(url); 10490 loc = location; 10491 10492 // If script is from same domain and we 10493 // use IE 6 then use XHR since it's more reliable 10494 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') { 10495 tinymce.util.XHR.send({ 10496 url : tinymce._addVer(uri.getURI()), 10497 success : function(content) { 10498 // Create new temp script element 10499 var script = dom.create('script', { 10500 type : 'text/javascript' 10501 }); 10502 10503 // Evaluate script in global scope 10504 script.text = content; 10505 document.getElementsByTagName('head')[0].appendChild(script); 10506 dom.remove(script); 10507 10508 done(); 10509 }, 10510 10511 error : error 10512 }); 10513 10514 return; 10515 } 10516 } 10517 10518 // Create new script element 10519 elm = document.createElement('script'); 10520 elm.id = id; 10521 elm.type = 'text/javascript'; 10522 elm.src = tinymce._addVer(url); 10523 10524 // Add onload listener for non IE browsers since IE9 10525 // fires onload event before the script is parsed and executed 10526 if (!tinymce.isIE) 10527 elm.onload = done; 10528 10529 // Add onerror event will get fired on some browsers but not all of them 10530 elm.onerror = error; 10531 10532 // Opera 9.60 doesn't seem to fire the onreadystate event at correctly 10533 if (!tinymce.isOpera) { 10534 elm.onreadystatechange = function() { 10535 var state = elm.readyState; 10536 10537 // Loaded state is passed on IE 6 however there 10538 // are known issues with this method but we can't use 10539 // XHR in a cross domain loading 10540 if (state == 'complete' || state == 'loaded') 10541 done(); 10542 }; 10543 } 10544 10545 // Most browsers support this feature so we report errors 10546 // for those at least to help users track their missing plugins etc 10547 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option 10548 /*elm.onerror = function() { 10549 alert('Failed to load: ' + url); 10550 };*/ 10551 10552 // Add script to document 10553 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); 10554 }; 10555 10556 this.isDone = function(url) { 10557 return states[url] == LOADED; 10558 }; 10559 10560 this.markDone = function(url) { 10561 states[url] = LOADED; 10562 }; 10563 10564 this.add = this.load = function(url, callback, scope) { 10565 var item, state = states[url]; 10566 10567 // Add url to load queue 10568 if (state == undef) { 10569 queue.push(url); 10570 states[url] = QUEUED; 10571 } 10572 10573 if (callback) { 10574 // Store away callback for later execution 10575 if (!scriptLoadedCallbacks[url]) 10576 scriptLoadedCallbacks[url] = []; 10577 10578 scriptLoadedCallbacks[url].push({ 10579 func : callback, 10580 scope : scope || this 10581 }); 10582 } 10583 }; 10584 10585 this.loadQueue = function(callback, scope) { 10586 this.loadScripts(queue, callback, scope); 10587 }; 10588 10589 this.loadScripts = function(scripts, callback, scope) { 10590 var loadScripts; 10591 10592 function execScriptLoadedCallbacks(url) { 10593 // Execute URL callback functions 10594 tinymce.each(scriptLoadedCallbacks[url], function(callback) { 10595 callback.func.call(callback.scope); 10596 }); 10597 10598 scriptLoadedCallbacks[url] = undef; 10599 }; 10600 10601 queueLoadedCallbacks.push({ 10602 func : callback, 10603 scope : scope || this 10604 }); 10605 10606 loadScripts = function() { 10607 var loadingScripts = tinymce.grep(scripts); 10608 10609 // Current scripts has been handled 10610 scripts.length = 0; 10611 10612 // Load scripts that needs to be loaded 10613 tinymce.each(loadingScripts, function(url) { 10614 // Script is already loaded then execute script callbacks directly 10615 if (states[url] == LOADED) { 10616 execScriptLoadedCallbacks(url); 10617 return; 10618 } 10619 10620 // Is script not loading then start loading it 10621 if (states[url] != LOADING) { 10622 states[url] = LOADING; 10623 loading++; 10624 10625 loadScript(url, function() { 10626 states[url] = LOADED; 10627 loading--; 10628 10629 execScriptLoadedCallbacks(url); 10630 10631 // Load more scripts if they where added by the recently loaded script 10632 loadScripts(); 10633 }); 10634 } 10635 }); 10636 10637 // No scripts are currently loading then execute all pending queue loaded callbacks 10638 if (!loading) { 10639 tinymce.each(queueLoadedCallbacks, function(callback) { 10640 callback.func.call(callback.scope); 10641 }); 10642 10643 queueLoadedCallbacks.length = 0; 10644 } 10645 }; 10646 10647 loadScripts(); 10648 }; 10649 }; 10650 10651 // Global script loader 10652 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader(); 10653 })(tinymce); 10654 10655 (function(tinymce) { 10656 tinymce.dom.RangeUtils = function(dom) { 10657 var INVISIBLE_CHAR = '\uFEFF'; 10658 10659 this.walk = function(rng, callback) { 10660 var startContainer = rng.startContainer, 10661 startOffset = rng.startOffset, 10662 endContainer = rng.endContainer, 10663 endOffset = rng.endOffset, 10664 ancestor, startPoint, 10665 endPoint, node, parent, siblings, nodes; 10666 10667 // Handle table cell selection the table plugin enables 10668 // you to fake select table cells and perform formatting actions on them 10669 nodes = dom.select('td.mceSelected,th.mceSelected'); 10670 if (nodes.length > 0) { 10671 tinymce.each(nodes, function(node) { 10672 callback([node]); 10673 }); 10674 10675 return; 10676 } 10677 10678 function exclude(nodes) { 10679 var node; 10680 10681 // First node is excluded 10682 node = nodes[0]; 10683 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { 10684 nodes.splice(0, 1); 10685 } 10686 10687 // Last node is excluded 10688 node = nodes[nodes.length - 1]; 10689 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { 10690 nodes.splice(nodes.length - 1, 1); 10691 } 10692 10693 return nodes; 10694 }; 10695 10696 function collectSiblings(node, name, end_node) { 10697 var siblings = []; 10698 10699 for (; node && node != end_node; node = node[name]) 10700 siblings.push(node); 10701 10702 return siblings; 10703 }; 10704 10705 function findEndPoint(node, root) { 10706 do { 10707 if (node.parentNode == root) 10708 return node; 10709 10710 node = node.parentNode; 10711 } while(node); 10712 }; 10713 10714 function walkBoundary(start_node, end_node, next) { 10715 var siblingName = next ? 'nextSibling' : 'previousSibling'; 10716 10717 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { 10718 parent = node.parentNode; 10719 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); 10720 10721 if (siblings.length) { 10722 if (!next) 10723 siblings.reverse(); 10724 10725 callback(exclude(siblings)); 10726 } 10727 } 10728 }; 10729 10730 // If index based start position then resolve it 10731 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) 10732 startContainer = startContainer.childNodes[startOffset]; 10733 10734 // If index based end position then resolve it 10735 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) 10736 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; 10737 10738 // Same container 10739 if (startContainer == endContainer) 10740 return callback(exclude([startContainer])); 10741 10742 // Find common ancestor and end points 10743 ancestor = dom.findCommonAncestor(startContainer, endContainer); 10744 10745 // Process left side 10746 for (node = startContainer; node; node = node.parentNode) { 10747 if (node === endContainer) 10748 return walkBoundary(startContainer, ancestor, true); 10749 10750 if (node === ancestor) 10751 break; 10752 } 10753 10754 // Process right side 10755 for (node = endContainer; node; node = node.parentNode) { 10756 if (node === startContainer) 10757 return walkBoundary(endContainer, ancestor); 10758 10759 if (node === ancestor) 10760 break; 10761 } 10762 10763 // Find start/end point 10764 startPoint = findEndPoint(startContainer, ancestor) || startContainer; 10765 endPoint = findEndPoint(endContainer, ancestor) || endContainer; 10766 10767 // Walk left leaf 10768 walkBoundary(startContainer, startPoint, true); 10769 10770 // Walk the middle from start to end point 10771 siblings = collectSiblings( 10772 startPoint == startContainer ? startPoint : startPoint.nextSibling, 10773 'nextSibling', 10774 endPoint == endContainer ? endPoint.nextSibling : endPoint 10775 ); 10776 10777 if (siblings.length) 10778 callback(exclude(siblings)); 10779 10780 // Walk right leaf 10781 walkBoundary(endContainer, endPoint); 10782 }; 10783 10784 this.split = function(rng) { 10785 var startContainer = rng.startContainer, 10786 startOffset = rng.startOffset, 10787 endContainer = rng.endContainer, 10788 endOffset = rng.endOffset; 10789 10790 function splitText(node, offset) { 10791 return node.splitText(offset); 10792 }; 10793 10794 // Handle single text node 10795 if (startContainer == endContainer && startContainer.nodeType == 3) { 10796 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { 10797 endContainer = splitText(startContainer, startOffset); 10798 startContainer = endContainer.previousSibling; 10799 10800 if (endOffset > startOffset) { 10801 endOffset = endOffset - startOffset; 10802 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; 10803 endOffset = endContainer.nodeValue.length; 10804 startOffset = 0; 10805 } else { 10806 endOffset = 0; 10807 } 10808 } 10809 } else { 10810 // Split startContainer text node if needed 10811 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { 10812 startContainer = splitText(startContainer, startOffset); 10813 startOffset = 0; 10814 } 10815 10816 // Split endContainer text node if needed 10817 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { 10818 endContainer = splitText(endContainer, endOffset).previousSibling; 10819 endOffset = endContainer.nodeValue.length; 10820 } 10821 } 10822 10823 return { 10824 startContainer : startContainer, 10825 startOffset : startOffset, 10826 endContainer : endContainer, 10827 endOffset : endOffset 10828 }; 10829 }; 10830 10831 }; 10832 10833 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) { 10834 if (rng1 && rng2) { 10835 // Compare native IE ranges 10836 if (rng1.item || rng1.duplicate) { 10837 // Both are control ranges and the selected element matches 10838 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) 10839 return true; 10840 10841 // Both are text ranges and the range matches 10842 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) 10843 return true; 10844 } else { 10845 // Compare w3c ranges 10846 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; 10847 } 10848 } 10849 10850 return false; 10851 }; 10852 })(tinymce); 10853 10854 (function(tinymce) { 10855 var Event = tinymce.dom.Event, each = tinymce.each; 10856 10857 tinymce.create('tinymce.ui.KeyboardNavigation', { 10858 KeyboardNavigation: function(settings, dom) { 10859 var t = this, root = settings.root, items = settings.items, 10860 enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown, 10861 excludeFromTabOrder = settings.excludeFromTabOrder, 10862 itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId; 10863 10864 dom = dom || tinymce.DOM; 10865 10866 itemFocussed = function(evt) { 10867 focussedId = evt.target.id; 10868 }; 10869 10870 itemBlurred = function(evt) { 10871 dom.setAttrib(evt.target.id, 'tabindex', '-1'); 10872 }; 10873 10874 rootFocussed = function(evt) { 10875 var item = dom.get(focussedId); 10876 dom.setAttrib(item, 'tabindex', '0'); 10877 item.focus(); 10878 }; 10879 10880 t.focus = function() { 10881 dom.get(focussedId).focus(); 10882 }; 10883 10884 t.destroy = function() { 10885 each(items, function(item) { 10886 var elm = dom.get(item.id); 10887 10888 dom.unbind(elm, 'focus', itemFocussed); 10889 dom.unbind(elm, 'blur', itemBlurred); 10890 }); 10891 10892 var rootElm = dom.get(root); 10893 dom.unbind(rootElm, 'focus', rootFocussed); 10894 dom.unbind(rootElm, 'keydown', rootKeydown); 10895 10896 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null; 10897 t.destroy = function() {}; 10898 }; 10899 10900 t.moveFocus = function(dir, evt) { 10901 var idx = -1, controls = t.controls, newFocus; 10902 10903 if (!focussedId) 10904 return; 10905 10906 each(items, function(item, index) { 10907 if (item.id === focussedId) { 10908 idx = index; 10909 return false; 10910 } 10911 }); 10912 10913 idx += dir; 10914 if (idx < 0) { 10915 idx = items.length - 1; 10916 } else if (idx >= items.length) { 10917 idx = 0; 10918 } 10919 10920 newFocus = items[idx]; 10921 dom.setAttrib(focussedId, 'tabindex', '-1'); 10922 dom.setAttrib(newFocus.id, 'tabindex', '0'); 10923 dom.get(newFocus.id).focus(); 10924 10925 if (settings.actOnFocus) { 10926 settings.onAction(newFocus.id); 10927 } 10928 10929 if (evt) 10930 Event.cancel(evt); 10931 }; 10932 10933 rootKeydown = function(evt) { 10934 var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32; 10935 10936 switch (evt.keyCode) { 10937 case DOM_VK_LEFT: 10938 if (enableLeftRight) t.moveFocus(-1); 10939 break; 10940 10941 case DOM_VK_RIGHT: 10942 if (enableLeftRight) t.moveFocus(1); 10943 break; 10944 10945 case DOM_VK_UP: 10946 if (enableUpDown) t.moveFocus(-1); 10947 break; 10948 10949 case DOM_VK_DOWN: 10950 if (enableUpDown) t.moveFocus(1); 10951 break; 10952 10953 case DOM_VK_ESCAPE: 10954 if (settings.onCancel) { 10955 settings.onCancel(); 10956 Event.cancel(evt); 10957 } 10958 break; 10959 10960 case DOM_VK_ENTER: 10961 case DOM_VK_RETURN: 10962 case DOM_VK_SPACE: 10963 if (settings.onAction) { 10964 settings.onAction(focussedId); 10965 Event.cancel(evt); 10966 } 10967 break; 10968 } 10969 }; 10970 10971 // Set up state and listeners for each item. 10972 each(items, function(item, idx) { 10973 var tabindex, elm; 10974 10975 if (!item.id) { 10976 item.id = dom.uniqueId('_mce_item_'); 10977 } 10978 10979 elm = dom.get(item.id); 10980 10981 if (excludeFromTabOrder) { 10982 dom.bind(elm, 'blur', itemBlurred); 10983 tabindex = '-1'; 10984 } else { 10985 tabindex = (idx === 0 ? '0' : '-1'); 10986 } 10987 10988 elm.setAttribute('tabindex', tabindex); 10989 dom.bind(elm, 'focus', itemFocussed); 10990 }); 10991 10992 // Setup initial state for root element. 10993 if (items[0]){ 10994 focussedId = items[0].id; 10995 } 10996 10997 dom.setAttrib(root, 'tabindex', '-1'); 10998 10999 // Setup listeners for root element. 11000 var rootElm = dom.get(root); 11001 dom.bind(rootElm, 'focus', rootFocussed); 11002 dom.bind(rootElm, 'keydown', rootKeydown); 11003 } 11004 }); 11005 })(tinymce); 11006 11007 (function(tinymce) { 11008 // Shorten class names 11009 var DOM = tinymce.DOM, is = tinymce.is; 11010 11011 tinymce.create('tinymce.ui.Control', { 11012 Control : function(id, s, editor) { 11013 this.id = id; 11014 this.settings = s = s || {}; 11015 this.rendered = false; 11016 this.onRender = new tinymce.util.Dispatcher(this); 11017 this.classPrefix = ''; 11018 this.scope = s.scope || this; 11019 this.disabled = 0; 11020 this.active = 0; 11021 this.editor = editor; 11022 }, 11023 11024 setAriaProperty : function(property, value) { 11025 var element = DOM.get(this.id + '_aria') || DOM.get(this.id); 11026 if (element) { 11027 DOM.setAttrib(element, 'aria-' + property, !!value); 11028 } 11029 }, 11030 11031 focus : function() { 11032 DOM.get(this.id).focus(); 11033 }, 11034 11035 setDisabled : function(s) { 11036 if (s != this.disabled) { 11037 this.setAriaProperty('disabled', s); 11038 11039 this.setState('Disabled', s); 11040 this.setState('Enabled', !s); 11041 this.disabled = s; 11042 } 11043 }, 11044 11045 isDisabled : function() { 11046 return this.disabled; 11047 }, 11048 11049 setActive : function(s) { 11050 if (s != this.active) { 11051 this.setState('Active', s); 11052 this.active = s; 11053 this.setAriaProperty('pressed', s); 11054 } 11055 }, 11056 11057 isActive : function() { 11058 return this.active; 11059 }, 11060 11061 setState : function(c, s) { 11062 var n = DOM.get(this.id); 11063 11064 c = this.classPrefix + c; 11065 11066 if (s) 11067 DOM.addClass(n, c); 11068 else 11069 DOM.removeClass(n, c); 11070 }, 11071 11072 isRendered : function() { 11073 return this.rendered; 11074 }, 11075 11076 renderHTML : function() { 11077 }, 11078 11079 renderTo : function(n) { 11080 DOM.setHTML(n, this.renderHTML()); 11081 }, 11082 11083 postRender : function() { 11084 var t = this, b; 11085 11086 // Set pending states 11087 if (is(t.disabled)) { 11088 b = t.disabled; 11089 t.disabled = -1; 11090 t.setDisabled(b); 11091 } 11092 11093 if (is(t.active)) { 11094 b = t.active; 11095 t.active = -1; 11096 t.setActive(b); 11097 } 11098 }, 11099 11100 remove : function() { 11101 DOM.remove(this.id); 11102 this.destroy(); 11103 }, 11104 11105 destroy : function() { 11106 tinymce.dom.Event.clear(this.id); 11107 } 11108 }); 11109 })(tinymce); 11110 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', { 11111 Container : function(id, s, editor) { 11112 this.parent(id, s, editor); 11113 11114 this.controls = []; 11115 11116 this.lookup = {}; 11117 }, 11118 11119 add : function(c) { 11120 this.lookup[c.id] = c; 11121 this.controls.push(c); 11122 11123 return c; 11124 }, 11125 11126 get : function(n) { 11127 return this.lookup[n]; 11128 } 11129 }); 11130 11131 11132 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { 11133 Separator : function(id, s) { 11134 this.parent(id, s); 11135 this.classPrefix = 'mceSeparator'; 11136 this.setDisabled(true); 11137 }, 11138 11139 renderHTML : function() { 11140 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'}); 11141 } 11142 }); 11143 11144 (function(tinymce) { 11145 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11146 11147 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', { 11148 MenuItem : function(id, s) { 11149 this.parent(id, s); 11150 this.classPrefix = 'mceMenuItem'; 11151 }, 11152 11153 setSelected : function(s) { 11154 this.setState('Selected', s); 11155 this.setAriaProperty('checked', !!s); 11156 this.selected = s; 11157 }, 11158 11159 isSelected : function() { 11160 return this.selected; 11161 }, 11162 11163 postRender : function() { 11164 var t = this; 11165 11166 t.parent(); 11167 11168 // Set pending state 11169 if (is(t.selected)) 11170 t.setSelected(t.selected); 11171 } 11172 }); 11173 })(tinymce); 11174 11175 (function(tinymce) { 11176 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11177 11178 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', { 11179 Menu : function(id, s) { 11180 var t = this; 11181 11182 t.parent(id, s); 11183 t.items = {}; 11184 t.collapsed = false; 11185 t.menuCount = 0; 11186 t.onAddItem = new tinymce.util.Dispatcher(this); 11187 }, 11188 11189 expand : function(d) { 11190 var t = this; 11191 11192 if (d) { 11193 walk(t, function(o) { 11194 if (o.expand) 11195 o.expand(); 11196 }, 'items', t); 11197 } 11198 11199 t.collapsed = false; 11200 }, 11201 11202 collapse : function(d) { 11203 var t = this; 11204 11205 if (d) { 11206 walk(t, function(o) { 11207 if (o.collapse) 11208 o.collapse(); 11209 }, 'items', t); 11210 } 11211 11212 t.collapsed = true; 11213 }, 11214 11215 isCollapsed : function() { 11216 return this.collapsed; 11217 }, 11218 11219 add : function(o) { 11220 if (!o.settings) 11221 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o); 11222 11223 this.onAddItem.dispatch(this, o); 11224 11225 return this.items[o.id] = o; 11226 }, 11227 11228 addSeparator : function() { 11229 return this.add({separator : true}); 11230 }, 11231 11232 addMenu : function(o) { 11233 if (!o.collapse) 11234 o = this.createMenu(o); 11235 11236 this.menuCount++; 11237 11238 return this.add(o); 11239 }, 11240 11241 hasMenus : function() { 11242 return this.menuCount !== 0; 11243 }, 11244 11245 remove : function(o) { 11246 delete this.items[o.id]; 11247 }, 11248 11249 removeAll : function() { 11250 var t = this; 11251 11252 walk(t, function(o) { 11253 if (o.removeAll) 11254 o.removeAll(); 11255 else 11256 o.remove(); 11257 11258 o.destroy(); 11259 }, 'items', t); 11260 11261 t.items = {}; 11262 }, 11263 11264 createMenu : function(o) { 11265 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o); 11266 11267 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem); 11268 11269 return m; 11270 } 11271 }); 11272 })(tinymce); 11273 (function(tinymce) { 11274 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element; 11275 11276 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', { 11277 DropMenu : function(id, s) { 11278 s = s || {}; 11279 s.container = s.container || DOM.doc.body; 11280 s.offset_x = s.offset_x || 0; 11281 s.offset_y = s.offset_y || 0; 11282 s.vp_offset_x = s.vp_offset_x || 0; 11283 s.vp_offset_y = s.vp_offset_y || 0; 11284 11285 if (is(s.icons) && !s.icons) 11286 s['class'] += ' mceNoIcons'; 11287 11288 this.parent(id, s); 11289 this.onShowMenu = new tinymce.util.Dispatcher(this); 11290 this.onHideMenu = new tinymce.util.Dispatcher(this); 11291 this.classPrefix = 'mceMenu'; 11292 }, 11293 11294 createMenu : function(s) { 11295 var t = this, cs = t.settings, m; 11296 11297 s.container = s.container || cs.container; 11298 s.parent = t; 11299 s.constrain = s.constrain || cs.constrain; 11300 s['class'] = s['class'] || cs['class']; 11301 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x; 11302 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y; 11303 s.keyboard_focus = cs.keyboard_focus; 11304 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s); 11305 11306 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem); 11307 11308 return m; 11309 }, 11310 11311 focus : function() { 11312 var t = this; 11313 if (t.keyboardNav) { 11314 t.keyboardNav.focus(); 11315 } 11316 }, 11317 11318 update : function() { 11319 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th; 11320 11321 tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth; 11322 th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight; 11323 11324 if (!DOM.boxModel) 11325 t.element.setStyles({width : tw + 2, height : th + 2}); 11326 else 11327 t.element.setStyles({width : tw, height : th}); 11328 11329 if (s.max_width) 11330 DOM.setStyle(co, 'width', tw); 11331 11332 if (s.max_height) { 11333 DOM.setStyle(co, 'height', th); 11334 11335 if (tb.clientHeight < s.max_height) 11336 DOM.setStyle(co, 'overflow', 'hidden'); 11337 } 11338 }, 11339 11340 showMenu : function(x, y, px) { 11341 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix; 11342 11343 t.collapse(1); 11344 11345 if (t.isMenuVisible) 11346 return; 11347 11348 if (!t.rendered) { 11349 co = DOM.add(t.settings.container, t.renderNode()); 11350 11351 each(t.items, function(o) { 11352 o.postRender(); 11353 }); 11354 11355 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11356 } else 11357 co = DOM.get('menu_' + t.id); 11358 11359 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug 11360 if (!tinymce.isOpera) 11361 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF}); 11362 11363 DOM.show(co); 11364 t.update(); 11365 11366 x += s.offset_x || 0; 11367 y += s.offset_y || 0; 11368 vp.w -= 4; 11369 vp.h -= 4; 11370 11371 // Move inside viewport if not submenu 11372 if (s.constrain) { 11373 w = co.clientWidth - ot; 11374 h = co.clientHeight - ot; 11375 mx = vp.x + vp.w; 11376 my = vp.y + vp.h; 11377 11378 if ((x + s.vp_offset_x + w) > mx) 11379 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w); 11380 11381 if ((y + s.vp_offset_y + h) > my) 11382 y = Math.max(0, (my - s.vp_offset_y) - h); 11383 } 11384 11385 DOM.setStyles(co, {left : x , top : y}); 11386 t.element.update(); 11387 11388 t.isMenuVisible = 1; 11389 t.mouseClickFunc = Event.add(co, 'click', function(e) { 11390 var m; 11391 11392 e = e.target; 11393 11394 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) { 11395 m = t.items[e.id]; 11396 11397 if (m.isDisabled()) 11398 return; 11399 11400 dm = t; 11401 11402 while (dm) { 11403 if (dm.hideMenu) 11404 dm.hideMenu(); 11405 11406 dm = dm.settings.parent; 11407 } 11408 11409 if (m.settings.onclick) 11410 m.settings.onclick(e); 11411 11412 return false; // Cancel to fix onbeforeunload problem 11413 } 11414 }); 11415 11416 if (t.hasMenus()) { 11417 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) { 11418 var m, r, mi; 11419 11420 e = e.target; 11421 if (e && (e = DOM.getParent(e, 'tr'))) { 11422 m = t.items[e.id]; 11423 11424 if (t.lastMenu) 11425 t.lastMenu.collapse(1); 11426 11427 if (m.isDisabled()) 11428 return; 11429 11430 if (e && DOM.hasClass(e, cp + 'ItemSub')) { 11431 //p = DOM.getPos(s.container); 11432 r = DOM.getRect(e); 11433 m.showMenu((r.x + r.w - ot), r.y - ot, r.x); 11434 t.lastMenu = m; 11435 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive'); 11436 } 11437 } 11438 }); 11439 } 11440 11441 Event.add(co, 'keydown', t._keyHandler, t); 11442 11443 t.onShowMenu.dispatch(t); 11444 11445 if (s.keyboard_focus) { 11446 t._setupKeyboardNav(); 11447 } 11448 }, 11449 11450 hideMenu : function(c) { 11451 var t = this, co = DOM.get('menu_' + t.id), e; 11452 11453 if (!t.isMenuVisible) 11454 return; 11455 11456 if (t.keyboardNav) t.keyboardNav.destroy(); 11457 Event.remove(co, 'mouseover', t.mouseOverFunc); 11458 Event.remove(co, 'click', t.mouseClickFunc); 11459 Event.remove(co, 'keydown', t._keyHandler); 11460 DOM.hide(co); 11461 t.isMenuVisible = 0; 11462 11463 if (!c) 11464 t.collapse(1); 11465 11466 if (t.element) 11467 t.element.hide(); 11468 11469 if (e = DOM.get(t.id)) 11470 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive'); 11471 11472 t.onHideMenu.dispatch(t); 11473 }, 11474 11475 add : function(o) { 11476 var t = this, co; 11477 11478 o = t.parent(o); 11479 11480 if (t.isRendered && (co = DOM.get('menu_' + t.id))) 11481 t._add(DOM.select('tbody', co)[0], o); 11482 11483 return o; 11484 }, 11485 11486 collapse : function(d) { 11487 this.parent(d); 11488 this.hideMenu(1); 11489 }, 11490 11491 remove : function(o) { 11492 DOM.remove(o.id); 11493 this.destroy(); 11494 11495 return this.parent(o); 11496 }, 11497 11498 destroy : function() { 11499 var t = this, co = DOM.get('menu_' + t.id); 11500 11501 if (t.keyboardNav) t.keyboardNav.destroy(); 11502 Event.remove(co, 'mouseover', t.mouseOverFunc); 11503 Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc); 11504 Event.remove(co, 'click', t.mouseClickFunc); 11505 Event.remove(co, 'keydown', t._keyHandler); 11506 11507 if (t.element) 11508 t.element.remove(); 11509 11510 DOM.remove(co); 11511 }, 11512 11513 renderNode : function() { 11514 var t = this, s = t.settings, n, tb, co, w; 11515 11516 w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'}); 11517 if (t.settings.parent) { 11518 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id); 11519 } 11520 co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); 11521 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11522 11523 if (s.menu_line) 11524 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'}); 11525 11526 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'}); 11527 n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); 11528 tb = DOM.add(n, 'tbody'); 11529 11530 each(t.items, function(o) { 11531 t._add(tb, o); 11532 }); 11533 11534 t.rendered = true; 11535 11536 return w; 11537 }, 11538 11539 // Internal functions 11540 _setupKeyboardNav : function(){ 11541 var contextMenu, menuItems, t=this; 11542 contextMenu = DOM.get('menu_' + t.id); 11543 menuItems = DOM.select('a[role=option]', 'menu_' + t.id); 11544 menuItems.splice(0,0,contextMenu); 11545 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 11546 root: 'menu_' + t.id, 11547 items: menuItems, 11548 onCancel: function() { 11549 t.hideMenu(); 11550 }, 11551 enableUpDown: true 11552 }); 11553 contextMenu.focus(); 11554 }, 11555 11556 _keyHandler : function(evt) { 11557 var t = this, e; 11558 switch (evt.keyCode) { 11559 case 37: // Left 11560 if (t.settings.parent) { 11561 t.hideMenu(); 11562 t.settings.parent.focus(); 11563 Event.cancel(evt); 11564 } 11565 break; 11566 case 39: // Right 11567 if (t.mouseOverFunc) 11568 t.mouseOverFunc(evt); 11569 break; 11570 } 11571 }, 11572 11573 _add : function(tb, o) { 11574 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic; 11575 11576 if (s.separator) { 11577 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'}); 11578 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'}); 11579 11580 if (n = ro.previousSibling) 11581 DOM.addClass(n, 'mceLast'); 11582 11583 return; 11584 } 11585 11586 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'}); 11587 n = it = DOM.add(n, s.titleItem ? 'th' : 'td'); 11588 n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); 11589 11590 if (s.parent) { 11591 DOM.setAttrib(a, 'aria-haspopup', 'true'); 11592 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id); 11593 } 11594 11595 DOM.addClass(it, s['class']); 11596 // n = DOM.add(n, 'span', {'class' : 'item'}); 11597 11598 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')}); 11599 11600 if (s.icon_src) 11601 DOM.add(ic, 'img', {src : s.icon_src}); 11602 11603 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title); 11604 11605 if (o.settings.style) { 11606 if (typeof o.settings.style == "function") 11607 o.settings.style = o.settings.style(); 11608 11609 DOM.setAttrib(n, 'style', o.settings.style); 11610 } 11611 11612 if (tb.childNodes.length == 1) 11613 DOM.addClass(ro, 'mceFirst'); 11614 11615 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator')) 11616 DOM.addClass(ro, 'mceFirst'); 11617 11618 if (o.collapse) 11619 DOM.addClass(ro, cp + 'ItemSub'); 11620 11621 if (n = ro.previousSibling) 11622 DOM.removeClass(n, 'mceLast'); 11623 11624 DOM.addClass(ro, 'mceLast'); 11625 } 11626 }); 11627 })(tinymce); 11628 (function(tinymce) { 11629 var DOM = tinymce.DOM; 11630 11631 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', { 11632 Button : function(id, s, ed) { 11633 this.parent(id, s, ed); 11634 this.classPrefix = 'mceButton'; 11635 }, 11636 11637 renderHTML : function() { 11638 var cp = this.classPrefix, s = this.settings, h, l; 11639 11640 l = DOM.encode(s.label || ''); 11641 h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">'; 11642 if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) ) 11643 h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); 11644 else 11645 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); 11646 11647 h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 11648 h += '</a>'; 11649 return h; 11650 }, 11651 11652 postRender : function() { 11653 var t = this, s = t.settings, imgBookmark; 11654 11655 // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so 11656 // need to keep the selection in case the selection is lost 11657 if (tinymce.isIE && t.editor) { 11658 tinymce.dom.Event.add(t.id, 'mousedown', function(e) { 11659 var nodeName = t.editor.selection.getNode().nodeName; 11660 imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null; 11661 }); 11662 } 11663 tinymce.dom.Event.add(t.id, 'click', function(e) { 11664 if (!t.isDisabled()) { 11665 // restore the selection in case the selection is lost in IE 11666 if (tinymce.isIE && t.editor && imgBookmark !== null) { 11667 t.editor.selection.moveToBookmark(imgBookmark); 11668 } 11669 return s.onclick.call(s.scope, e); 11670 } 11671 }); 11672 tinymce.dom.Event.add(t.id, 'keyup', function(e) { 11673 if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) 11674 return s.onclick.call(s.scope, e); 11675 }); 11676 } 11677 }); 11678 })(tinymce); 11679 11680 (function(tinymce) { 11681 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 11682 11683 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', { 11684 ListBox : function(id, s, ed) { 11685 var t = this; 11686 11687 t.parent(id, s, ed); 11688 11689 t.items = []; 11690 11691 t.onChange = new Dispatcher(t); 11692 11693 t.onPostRender = new Dispatcher(t); 11694 11695 t.onAdd = new Dispatcher(t); 11696 11697 t.onRenderMenu = new tinymce.util.Dispatcher(this); 11698 11699 t.classPrefix = 'mceListBox'; 11700 t.marked = {}; 11701 }, 11702 11703 select : function(va) { 11704 var t = this, fv, f; 11705 11706 t.marked = {}; 11707 11708 if (va == undef) 11709 return t.selectByIndex(-1); 11710 11711 // Is string or number make function selector 11712 if (va && typeof(va)=="function") 11713 f = va; 11714 else { 11715 f = function(v) { 11716 return v == va; 11717 }; 11718 } 11719 11720 // Do we need to do something? 11721 if (va != t.selectedValue) { 11722 // Find item 11723 each(t.items, function(o, i) { 11724 if (f(o.value)) { 11725 fv = 1; 11726 t.selectByIndex(i); 11727 return false; 11728 } 11729 }); 11730 11731 if (!fv) 11732 t.selectByIndex(-1); 11733 } 11734 }, 11735 11736 selectByIndex : function(idx) { 11737 var t = this, e, o, label; 11738 11739 t.marked = {}; 11740 11741 if (idx != t.selectedIndex) { 11742 e = DOM.get(t.id + '_text'); 11743 label = DOM.get(t.id + '_voiceDesc'); 11744 o = t.items[idx]; 11745 11746 if (o) { 11747 t.selectedValue = o.value; 11748 t.selectedIndex = idx; 11749 DOM.setHTML(e, DOM.encode(o.title)); 11750 DOM.setHTML(label, t.settings.title + " - " + o.title); 11751 DOM.removeClass(e, 'mceTitle'); 11752 DOM.setAttrib(t.id, 'aria-valuenow', o.title); 11753 } else { 11754 DOM.setHTML(e, DOM.encode(t.settings.title)); 11755 DOM.setHTML(label, DOM.encode(t.settings.title)); 11756 DOM.addClass(e, 'mceTitle'); 11757 t.selectedValue = t.selectedIndex = null; 11758 DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title); 11759 } 11760 e = 0; 11761 } 11762 }, 11763 11764 mark : function(value) { 11765 this.marked[value] = true; 11766 }, 11767 11768 add : function(n, v, o) { 11769 var t = this; 11770 11771 o = o || {}; 11772 o = tinymce.extend(o, { 11773 title : n, 11774 value : v 11775 }); 11776 11777 t.items.push(o); 11778 t.onAdd.dispatch(t, o); 11779 }, 11780 11781 getLength : function() { 11782 return this.items.length; 11783 }, 11784 11785 renderHTML : function() { 11786 var h = '', t = this, s = t.settings, cp = t.classPrefix; 11787 11788 h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>'; 11789 h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 11790 h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>'; 11791 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>'; 11792 h += '</tr></tbody></table></span>'; 11793 11794 return h; 11795 }, 11796 11797 showMenu : function() { 11798 var t = this, p2, e = DOM.get(this.id), m; 11799 11800 if (t.isDisabled() || t.items.length === 0) 11801 return; 11802 11803 if (t.menu && t.menu.isMenuVisible) 11804 return t.hideMenu(); 11805 11806 if (!t.isMenuRendered) { 11807 t.renderMenu(); 11808 t.isMenuRendered = true; 11809 } 11810 11811 p2 = DOM.getPos(e); 11812 11813 m = t.menu; 11814 m.settings.offset_x = p2.x; 11815 m.settings.offset_y = p2.y; 11816 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus 11817 11818 // Select in menu 11819 each(t.items, function(o) { 11820 if (m.items[o.id]) { 11821 m.items[o.id].setSelected(0); 11822 } 11823 }); 11824 11825 each(t.items, function(o) { 11826 if (m.items[o.id] && t.marked[o.value]) { 11827 m.items[o.id].setSelected(1); 11828 } 11829 11830 if (o.value === t.selectedValue) { 11831 m.items[o.id].setSelected(1); 11832 } 11833 }); 11834 11835 m.showMenu(0, e.clientHeight); 11836 11837 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 11838 DOM.addClass(t.id, t.classPrefix + 'Selected'); 11839 11840 //DOM.get(t.id + '_text').focus(); 11841 }, 11842 11843 hideMenu : function(e) { 11844 var t = this; 11845 11846 if (t.menu && t.menu.isMenuVisible) { 11847 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 11848 11849 // Prevent double toogles by canceling the mouse click event to the button 11850 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open')) 11851 return; 11852 11853 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 11854 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 11855 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 11856 t.menu.hideMenu(); 11857 } 11858 } 11859 }, 11860 11861 renderMenu : function() { 11862 var t = this, m; 11863 11864 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 11865 menu_line : 1, 11866 'class' : t.classPrefix + 'Menu mceNoIcons', 11867 max_width : 250, 11868 max_height : 150 11869 }); 11870 11871 m.onHideMenu.add(function() { 11872 t.hideMenu(); 11873 t.focus(); 11874 }); 11875 11876 m.add({ 11877 title : t.settings.title, 11878 'class' : 'mceMenuItemTitle', 11879 onclick : function() { 11880 if (t.settings.onselect('') !== false) 11881 t.select(''); // Must be runned after 11882 } 11883 }); 11884 11885 each(t.items, function(o) { 11886 // No value then treat it as a title 11887 if (o.value === undef) { 11888 m.add({ 11889 title : o.title, 11890 role : "option", 11891 'class' : 'mceMenuItemTitle', 11892 onclick : function() { 11893 if (t.settings.onselect('') !== false) 11894 t.select(''); // Must be runned after 11895 } 11896 }); 11897 } else { 11898 o.id = DOM.uniqueId(); 11899 o.role= "option"; 11900 o.onclick = function() { 11901 if (t.settings.onselect(o.value) !== false) 11902 t.select(o.value); // Must be runned after 11903 }; 11904 11905 m.add(o); 11906 } 11907 }); 11908 11909 t.onRenderMenu.dispatch(t, m); 11910 t.menu = m; 11911 }, 11912 11913 postRender : function() { 11914 var t = this, cp = t.classPrefix; 11915 11916 Event.add(t.id, 'click', t.showMenu, t); 11917 Event.add(t.id, 'keydown', function(evt) { 11918 if (evt.keyCode == 32) { // Space 11919 t.showMenu(evt); 11920 Event.cancel(evt); 11921 } 11922 }); 11923 Event.add(t.id, 'focus', function() { 11924 if (!t._focused) { 11925 t.keyDownHandler = Event.add(t.id, 'keydown', function(e) { 11926 if (e.keyCode == 40) { 11927 t.showMenu(); 11928 Event.cancel(e); 11929 } 11930 }); 11931 t.keyPressHandler = Event.add(t.id, 'keypress', function(e) { 11932 var v; 11933 if (e.keyCode == 13) { 11934 // Fake select on enter 11935 v = t.selectedValue; 11936 t.selectedValue = null; // Needs to be null to fake change 11937 Event.cancel(e); 11938 t.settings.onselect(v); 11939 } 11940 }); 11941 } 11942 11943 t._focused = 1; 11944 }); 11945 Event.add(t.id, 'blur', function() { 11946 Event.remove(t.id, 'keydown', t.keyDownHandler); 11947 Event.remove(t.id, 'keypress', t.keyPressHandler); 11948 t._focused = 0; 11949 }); 11950 11951 // Old IE doesn't have hover on all elements 11952 if (tinymce.isIE6 || !DOM.boxModel) { 11953 Event.add(t.id, 'mouseover', function() { 11954 if (!DOM.hasClass(t.id, cp + 'Disabled')) 11955 DOM.addClass(t.id, cp + 'Hover'); 11956 }); 11957 11958 Event.add(t.id, 'mouseout', function() { 11959 if (!DOM.hasClass(t.id, cp + 'Disabled')) 11960 DOM.removeClass(t.id, cp + 'Hover'); 11961 }); 11962 } 11963 11964 t.onPostRender.dispatch(t, DOM.get(t.id)); 11965 }, 11966 11967 destroy : function() { 11968 this.parent(); 11969 11970 Event.clear(this.id + '_text'); 11971 Event.clear(this.id + '_open'); 11972 } 11973 }); 11974 })(tinymce); 11975 11976 (function(tinymce) { 11977 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 11978 11979 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', { 11980 NativeListBox : function(id, s) { 11981 this.parent(id, s); 11982 this.classPrefix = 'mceNativeListBox'; 11983 }, 11984 11985 setDisabled : function(s) { 11986 DOM.get(this.id).disabled = s; 11987 this.setAriaProperty('disabled', s); 11988 }, 11989 11990 isDisabled : function() { 11991 return DOM.get(this.id).disabled; 11992 }, 11993 11994 select : function(va) { 11995 var t = this, fv, f; 11996 11997 if (va == undef) 11998 return t.selectByIndex(-1); 11999 12000 // Is string or number make function selector 12001 if (va && typeof(va)=="function") 12002 f = va; 12003 else { 12004 f = function(v) { 12005 return v == va; 12006 }; 12007 } 12008 12009 // Do we need to do something? 12010 if (va != t.selectedValue) { 12011 // Find item 12012 each(t.items, function(o, i) { 12013 if (f(o.value)) { 12014 fv = 1; 12015 t.selectByIndex(i); 12016 return false; 12017 } 12018 }); 12019 12020 if (!fv) 12021 t.selectByIndex(-1); 12022 } 12023 }, 12024 12025 selectByIndex : function(idx) { 12026 DOM.get(this.id).selectedIndex = idx + 1; 12027 this.selectedValue = this.items[idx] ? this.items[idx].value : null; 12028 }, 12029 12030 add : function(n, v, a) { 12031 var o, t = this; 12032 12033 a = a || {}; 12034 a.value = v; 12035 12036 if (t.isRendered()) 12037 DOM.add(DOM.get(this.id), 'option', a, n); 12038 12039 o = { 12040 title : n, 12041 value : v, 12042 attribs : a 12043 }; 12044 12045 t.items.push(o); 12046 t.onAdd.dispatch(t, o); 12047 }, 12048 12049 getLength : function() { 12050 return this.items.length; 12051 }, 12052 12053 renderHTML : function() { 12054 var h, t = this; 12055 12056 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --'); 12057 12058 each(t.items, function(it) { 12059 h += DOM.createHTML('option', {value : it.value}, it.title); 12060 }); 12061 12062 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h); 12063 h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title); 12064 return h; 12065 }, 12066 12067 postRender : function() { 12068 var t = this, ch, changeListenerAdded = true; 12069 12070 t.rendered = true; 12071 12072 function onChange(e) { 12073 var v = t.items[e.target.selectedIndex - 1]; 12074 12075 if (v && (v = v.value)) { 12076 t.onChange.dispatch(t, v); 12077 12078 if (t.settings.onselect) 12079 t.settings.onselect(v); 12080 } 12081 }; 12082 12083 Event.add(t.id, 'change', onChange); 12084 12085 // Accessibility keyhandler 12086 Event.add(t.id, 'keydown', function(e) { 12087 var bf; 12088 12089 Event.remove(t.id, 'change', ch); 12090 changeListenerAdded = false; 12091 12092 bf = Event.add(t.id, 'blur', function() { 12093 if (changeListenerAdded) return; 12094 changeListenerAdded = true; 12095 Event.add(t.id, 'change', onChange); 12096 Event.remove(t.id, 'blur', bf); 12097 }); 12098 12099 //prevent default left and right keys on chrome - so that the keyboard navigation is used. 12100 if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) { 12101 return Event.prevent(e); 12102 } 12103 12104 if (e.keyCode == 13 || e.keyCode == 32) { 12105 onChange(e); 12106 return Event.cancel(e); 12107 } 12108 }); 12109 12110 t.onPostRender.dispatch(t, DOM.get(t.id)); 12111 } 12112 }); 12113 })(tinymce); 12114 12115 (function(tinymce) { 12116 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12117 12118 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', { 12119 MenuButton : function(id, s, ed) { 12120 this.parent(id, s, ed); 12121 12122 this.onRenderMenu = new tinymce.util.Dispatcher(this); 12123 12124 s.menu_container = s.menu_container || DOM.doc.body; 12125 }, 12126 12127 showMenu : function() { 12128 var t = this, p1, p2, e = DOM.get(t.id), m; 12129 12130 if (t.isDisabled()) 12131 return; 12132 12133 if (!t.isMenuRendered) { 12134 t.renderMenu(); 12135 t.isMenuRendered = true; 12136 } 12137 12138 if (t.isMenuVisible) 12139 return t.hideMenu(); 12140 12141 p1 = DOM.getPos(t.settings.menu_container); 12142 p2 = DOM.getPos(e); 12143 12144 m = t.menu; 12145 m.settings.offset_x = p2.x; 12146 m.settings.offset_y = p2.y; 12147 m.settings.vp_offset_x = p2.x; 12148 m.settings.vp_offset_y = p2.y; 12149 m.settings.keyboard_focus = t._focused; 12150 m.showMenu(0, e.firstChild.clientHeight); 12151 12152 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12153 t.setState('Selected', 1); 12154 12155 t.isMenuVisible = 1; 12156 }, 12157 12158 renderMenu : function() { 12159 var t = this, m; 12160 12161 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 12162 menu_line : 1, 12163 'class' : this.classPrefix + 'Menu', 12164 icons : t.settings.icons 12165 }); 12166 12167 m.onHideMenu.add(function() { 12168 t.hideMenu(); 12169 t.focus(); 12170 }); 12171 12172 t.onRenderMenu.dispatch(t, m); 12173 t.menu = m; 12174 }, 12175 12176 hideMenu : function(e) { 12177 var t = this; 12178 12179 // Prevent double toogles by canceling the mouse click event to the button 12180 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';})) 12181 return; 12182 12183 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 12184 t.setState('Selected', 0); 12185 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12186 if (t.menu) 12187 t.menu.hideMenu(); 12188 } 12189 12190 t.isMenuVisible = 0; 12191 }, 12192 12193 postRender : function() { 12194 var t = this, s = t.settings; 12195 12196 Event.add(t.id, 'click', function() { 12197 if (!t.isDisabled()) { 12198 if (s.onclick) 12199 s.onclick(t.value); 12200 12201 t.showMenu(); 12202 } 12203 }); 12204 } 12205 }); 12206 })(tinymce); 12207 12208 (function(tinymce) { 12209 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12210 12211 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', { 12212 SplitButton : function(id, s, ed) { 12213 this.parent(id, s, ed); 12214 this.classPrefix = 'mceSplitButton'; 12215 }, 12216 12217 renderHTML : function() { 12218 var h, t = this, s = t.settings, h1; 12219 12220 h = '<tbody><tr>'; 12221 12222 if (s.image) 12223 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']}); 12224 else 12225 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, ''); 12226 12227 h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title); 12228 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>'; 12229 12230 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>'); 12231 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>'; 12232 12233 h += '</tr></tbody>'; 12234 h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h); 12235 return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h); 12236 }, 12237 12238 postRender : function() { 12239 var t = this, s = t.settings, activate; 12240 12241 if (s.onclick) { 12242 activate = function(evt) { 12243 if (!t.isDisabled()) { 12244 s.onclick(t.value); 12245 Event.cancel(evt); 12246 } 12247 }; 12248 Event.add(t.id + '_action', 'click', activate); 12249 Event.add(t.id, ['click', 'keydown'], function(evt) { 12250 var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40; 12251 if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) { 12252 activate(); 12253 Event.cancel(evt); 12254 } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) { 12255 t.showMenu(); 12256 Event.cancel(evt); 12257 } 12258 }); 12259 } 12260 12261 Event.add(t.id + '_open', 'click', function (evt) { 12262 t.showMenu(); 12263 Event.cancel(evt); 12264 }); 12265 Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;}); 12266 Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;}); 12267 12268 // Old IE doesn't have hover on all elements 12269 if (tinymce.isIE6 || !DOM.boxModel) { 12270 Event.add(t.id, 'mouseover', function() { 12271 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12272 DOM.addClass(t.id, 'mceSplitButtonHover'); 12273 }); 12274 12275 Event.add(t.id, 'mouseout', function() { 12276 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12277 DOM.removeClass(t.id, 'mceSplitButtonHover'); 12278 }); 12279 } 12280 }, 12281 12282 destroy : function() { 12283 this.parent(); 12284 12285 Event.clear(this.id + '_action'); 12286 Event.clear(this.id + '_open'); 12287 Event.clear(this.id); 12288 } 12289 }); 12290 })(tinymce); 12291 12292 (function(tinymce) { 12293 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; 12294 12295 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { 12296 ColorSplitButton : function(id, s, ed) { 12297 var t = this; 12298 12299 t.parent(id, s, ed); 12300 12301 t.settings = s = tinymce.extend({ 12302 colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF', 12303 grid_width : 8, 12304 default_color : '#888888' 12305 }, t.settings); 12306 12307 t.onShowMenu = new tinymce.util.Dispatcher(t); 12308 12309 t.onHideMenu = new tinymce.util.Dispatcher(t); 12310 12311 t.value = s.default_color; 12312 }, 12313 12314 showMenu : function() { 12315 var t = this, r, p, e, p2; 12316 12317 if (t.isDisabled()) 12318 return; 12319 12320 if (!t.isMenuRendered) { 12321 t.renderMenu(); 12322 t.isMenuRendered = true; 12323 } 12324 12325 if (t.isMenuVisible) 12326 return t.hideMenu(); 12327 12328 e = DOM.get(t.id); 12329 DOM.show(t.id + '_menu'); 12330 DOM.addClass(e, 'mceSplitButtonSelected'); 12331 p2 = DOM.getPos(e); 12332 DOM.setStyles(t.id + '_menu', { 12333 left : p2.x, 12334 top : p2.y + e.firstChild.clientHeight, 12335 zIndex : 200000 12336 }); 12337 e = 0; 12338 12339 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12340 t.onShowMenu.dispatch(t); 12341 12342 if (t._focused) { 12343 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { 12344 if (e.keyCode == 27) 12345 t.hideMenu(); 12346 }); 12347 12348 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link 12349 } 12350 12351 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 12352 root: t.id + '_menu', 12353 items: DOM.select('a', t.id + '_menu'), 12354 onCancel: function() { 12355 t.hideMenu(); 12356 t.focus(); 12357 } 12358 }); 12359 12360 t.keyboardNav.focus(); 12361 t.isMenuVisible = 1; 12362 }, 12363 12364 hideMenu : function(e) { 12365 var t = this; 12366 12367 if (t.isMenuVisible) { 12368 // Prevent double toogles by canceling the mouse click event to the button 12369 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) 12370 return; 12371 12372 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { 12373 DOM.removeClass(t.id, 'mceSplitButtonSelected'); 12374 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12375 Event.remove(t.id + '_menu', 'keydown', t._keyHandler); 12376 DOM.hide(t.id + '_menu'); 12377 } 12378 12379 t.isMenuVisible = 0; 12380 t.onHideMenu.dispatch(); 12381 t.keyboardNav.destroy(); 12382 } 12383 }, 12384 12385 renderMenu : function() { 12386 var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context; 12387 12388 w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'}); 12389 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); 12390 DOM.add(m, 'span', {'class' : 'mceMenuLine'}); 12391 12392 n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'}); 12393 tb = DOM.add(n, 'tbody'); 12394 12395 // Generate color grid 12396 i = 0; 12397 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) { 12398 c = c.replace(/^#/, ''); 12399 12400 if (!i--) { 12401 tr = DOM.add(tb, 'tr'); 12402 i = s.grid_width - 1; 12403 } 12404 12405 n = DOM.add(tr, 'td'); 12406 var settings = { 12407 href : 'javascript:;', 12408 style : { 12409 backgroundColor : '#' + c 12410 }, 12411 'title': t.editor.getLang('colors.' + c, c), 12412 'data-mce-color' : '#' + c 12413 }; 12414 12415 // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE. 12416 if (!tinymce.isIE ) { 12417 settings.role = 'option'; 12418 } 12419 12420 n = DOM.add(n, 'a', settings); 12421 12422 if (t.editor.forcedHighContrastMode) { 12423 n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' }); 12424 if (n.getContext && (context = n.getContext("2d"))) { 12425 context.fillStyle = '#' + c; 12426 context.fillRect(0, 0, 16, 16); 12427 } else { 12428 // No point leaving a canvas element around if it's not supported for drawing on anyway. 12429 DOM.remove(n); 12430 } 12431 } 12432 }); 12433 12434 if (s.more_colors_func) { 12435 n = DOM.add(tb, 'tr'); 12436 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); 12437 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); 12438 12439 Event.add(n, 'click', function(e) { 12440 s.more_colors_func.call(s.more_colors_scope || this); 12441 return Event.cancel(e); // Cancel to fix onbeforeunload problem 12442 }); 12443 } 12444 12445 DOM.addClass(m, 'mceColorSplitMenu'); 12446 12447 // Prevent IE from scrolling and hindering click to occur #4019 12448 Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);}); 12449 12450 Event.add(t.id + '_menu', 'click', function(e) { 12451 var c; 12452 12453 e = DOM.getParent(e.target, 'a', tb); 12454 12455 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color'))) 12456 t.setColor(c); 12457 12458 return false; // Prevent IE auto save warning 12459 }); 12460 12461 return w; 12462 }, 12463 12464 setColor : function(c) { 12465 this.displayColor(c); 12466 this.hideMenu(); 12467 this.settings.onselect(c); 12468 }, 12469 12470 displayColor : function(c) { 12471 var t = this; 12472 12473 DOM.setStyle(t.id + '_preview', 'backgroundColor', c); 12474 12475 t.value = c; 12476 }, 12477 12478 postRender : function() { 12479 var t = this, id = t.id; 12480 12481 t.parent(); 12482 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); 12483 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value); 12484 }, 12485 12486 destroy : function() { 12487 var self = this; 12488 12489 self.parent(); 12490 12491 Event.clear(self.id + '_menu'); 12492 Event.clear(self.id + '_more'); 12493 DOM.remove(self.id + '_menu'); 12494 12495 if (self.keyboardNav) { 12496 self.keyboardNav.destroy(); 12497 } 12498 } 12499 }); 12500 })(tinymce); 12501 12502 (function(tinymce) { 12503 // Shorten class names 12504 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event; 12505 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', { 12506 renderHTML : function() { 12507 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings; 12508 12509 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">'); 12510 //TODO: ACC test this out - adding a role = application for getting the landmarks working well. 12511 h.push("<span role='application'>"); 12512 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>'); 12513 each(controls, function(toolbar) { 12514 h.push(toolbar.renderHTML()); 12515 }); 12516 h.push("</span>"); 12517 h.push('</div>'); 12518 12519 return h.join(''); 12520 }, 12521 12522 focus : function() { 12523 var t = this; 12524 dom.get(t.id).focus(); 12525 }, 12526 12527 postRender : function() { 12528 var t = this, items = []; 12529 12530 each(t.controls, function(toolbar) { 12531 each (toolbar.controls, function(control) { 12532 if (control.id) { 12533 items.push(control); 12534 } 12535 }); 12536 }); 12537 12538 t.keyNav = new tinymce.ui.KeyboardNavigation({ 12539 root: t.id, 12540 items: items, 12541 onCancel: function() { 12542 //Move focus if webkit so that navigation back will read the item. 12543 if (tinymce.isWebKit) { 12544 dom.get(t.editor.id+"_ifr").focus(); 12545 } 12546 t.editor.focus(); 12547 }, 12548 excludeFromTabOrder: !t.settings.tab_focus_toolbar 12549 }); 12550 }, 12551 12552 destroy : function() { 12553 var self = this; 12554 12555 self.parent(); 12556 self.keyNav.destroy(); 12557 Event.clear(self.id); 12558 } 12559 }); 12560 })(tinymce); 12561 12562 (function(tinymce) { 12563 // Shorten class names 12564 var dom = tinymce.DOM, each = tinymce.each; 12565 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { 12566 renderHTML : function() { 12567 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl; 12568 12569 cl = t.controls; 12570 for (i=0; i<cl.length; i++) { 12571 // Get current control, prev control, next control and if the control is a list box or not 12572 co = cl[i]; 12573 pr = cl[i - 1]; 12574 nx = cl[i + 1]; 12575 12576 // Add toolbar start 12577 if (i === 0) { 12578 c = 'mceToolbarStart'; 12579 12580 if (co.Button) 12581 c += ' mceToolbarStartButton'; 12582 else if (co.SplitButton) 12583 c += ' mceToolbarStartSplitButton'; 12584 else if (co.ListBox) 12585 c += ' mceToolbarStartListBox'; 12586 12587 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12588 } 12589 12590 // Add toolbar end before list box and after the previous button 12591 // This is to fix the o2k7 editor skins 12592 if (pr && co.ListBox) { 12593 if (pr.Button || pr.SplitButton) 12594 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->')); 12595 } 12596 12597 // Render control HTML 12598 12599 // IE 8 quick fix, needed to propertly generate a hit area for anchors 12600 if (dom.stdMode) 12601 h += '<td style="position: relative">' + co.renderHTML() + '</td>'; 12602 else 12603 h += '<td>' + co.renderHTML() + '</td>'; 12604 12605 // Add toolbar start after list box and before the next button 12606 // This is to fix the o2k7 editor skins 12607 if (nx && co.ListBox) { 12608 if (nx.Button || nx.SplitButton) 12609 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->')); 12610 } 12611 } 12612 12613 c = 'mceToolbarEnd'; 12614 12615 if (co.Button) 12616 c += ' mceToolbarEndButton'; 12617 else if (co.SplitButton) 12618 c += ' mceToolbarEndSplitButton'; 12619 else if (co.ListBox) 12620 c += ' mceToolbarEndListBox'; 12621 12622 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12623 12624 return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>'); 12625 } 12626 }); 12627 })(tinymce); 12628 12629 (function(tinymce) { 12630 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each; 12631 12632 tinymce.create('tinymce.AddOnManager', { 12633 AddOnManager : function() { 12634 var self = this; 12635 12636 self.items = []; 12637 self.urls = {}; 12638 self.lookup = {}; 12639 self.onAdd = new Dispatcher(self); 12640 }, 12641 12642 get : function(n) { 12643 if (this.lookup[n]) { 12644 return this.lookup[n].instance; 12645 } else { 12646 return undefined; 12647 } 12648 }, 12649 12650 dependencies : function(n) { 12651 var result; 12652 if (this.lookup[n]) { 12653 result = this.lookup[n].dependencies; 12654 } 12655 return result || []; 12656 }, 12657 12658 requireLangPack : function(n) { 12659 var s = tinymce.settings; 12660 12661 if (s && s.language && s.language_load !== false) 12662 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js'); 12663 }, 12664 12665 add : function(id, o, dependencies) { 12666 this.items.push(o); 12667 this.lookup[id] = {instance:o, dependencies:dependencies}; 12668 this.onAdd.dispatch(this, id, o); 12669 12670 return o; 12671 }, 12672 createUrl: function(baseUrl, dep) { 12673 if (typeof dep === "object") { 12674 return dep 12675 } else { 12676 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; 12677 } 12678 }, 12679 12680 addComponents: function(pluginName, scripts) { 12681 var pluginUrl = this.urls[pluginName]; 12682 tinymce.each(scripts, function(script){ 12683 tinymce.ScriptLoader.add(pluginUrl+"/"+script); 12684 }); 12685 }, 12686 12687 load : function(n, u, cb, s) { 12688 var t = this, url = u; 12689 12690 function loadDependencies() { 12691 var dependencies = t.dependencies(n); 12692 tinymce.each(dependencies, function(dep) { 12693 var newUrl = t.createUrl(u, dep); 12694 t.load(newUrl.resource, newUrl, undefined, undefined); 12695 }); 12696 if (cb) { 12697 if (s) { 12698 cb.call(s); 12699 } else { 12700 cb.call(tinymce.ScriptLoader); 12701 } 12702 } 12703 } 12704 12705 if (t.urls[n]) 12706 return; 12707 if (typeof u === "object") 12708 url = u.prefix + u.resource + u.suffix; 12709 12710 if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) 12711 url = tinymce.baseURL + '/' + url; 12712 12713 t.urls[n] = url.substring(0, url.lastIndexOf('/')); 12714 12715 if (t.lookup[n]) { 12716 loadDependencies(); 12717 } else { 12718 tinymce.ScriptLoader.add(url, loadDependencies, s); 12719 } 12720 } 12721 }); 12722 12723 // Create plugin and theme managers 12724 tinymce.PluginManager = new tinymce.AddOnManager(); 12725 tinymce.ThemeManager = new tinymce.AddOnManager(); 12726 }(tinymce)); 12727 12728 (function(tinymce) { 12729 // Shorten names 12730 var each = tinymce.each, extend = tinymce.extend, 12731 DOM = tinymce.DOM, Event = tinymce.dom.Event, 12732 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 12733 explode = tinymce.explode, 12734 Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0; 12735 12736 // Setup some URLs where the editor API is located and where the document is 12737 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); 12738 if (!/[\/\\]$/.test(tinymce.documentBaseURL)) 12739 tinymce.documentBaseURL += '/'; 12740 12741 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); 12742 12743 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); 12744 12745 // Add before unload listener 12746 // This was required since IE was leaking memory if you added and removed beforeunload listeners 12747 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event 12748 tinymce.onBeforeUnload = new Dispatcher(tinymce); 12749 12750 // Must be on window or IE will leak if the editor is placed in frame or iframe 12751 Event.add(window, 'beforeunload', function(e) { 12752 tinymce.onBeforeUnload.dispatch(tinymce, e); 12753 }); 12754 12755 tinymce.onAddEditor = new Dispatcher(tinymce); 12756 12757 tinymce.onRemoveEditor = new Dispatcher(tinymce); 12758 12759 tinymce.EditorManager = extend(tinymce, { 12760 editors : [], 12761 12762 i18n : {}, 12763 12764 activeEditor : null, 12765 12766 init : function(s) { 12767 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; 12768 12769 function createId(elm) { 12770 var id = elm.id; 12771 12772 // Use element id, or unique name or generate a unique id 12773 if (!id) { 12774 id = elm.name; 12775 12776 if (id && !DOM.get(id)) { 12777 id = elm.name; 12778 } else { 12779 // Generate unique name 12780 id = DOM.uniqueId(); 12781 } 12782 12783 elm.setAttribute('id', id); 12784 } 12785 12786 return id; 12787 }; 12788 12789 function execCallback(se, n, s) { 12790 var f = se[n]; 12791 12792 if (!f) 12793 return; 12794 12795 if (tinymce.is(f, 'string')) { 12796 s = f.replace(/\.\w+$/, ''); 12797 s = s ? tinymce.resolve(s) : 0; 12798 f = tinymce.resolve(f); 12799 } 12800 12801 return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); 12802 }; 12803 12804 function hasClass(n, c) { 12805 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); 12806 }; 12807 12808 t.settings = s; 12809 12810 // Legacy call 12811 Event.bind(window, 'ready', function() { 12812 var l, co; 12813 12814 execCallback(s, 'onpageload'); 12815 12816 switch (s.mode) { 12817 case "exact": 12818 l = s.elements || ''; 12819 12820 if(l.length > 0) { 12821 each(explode(l), function(v) { 12822 if (DOM.get(v)) { 12823 ed = new tinymce.Editor(v, s); 12824 el.push(ed); 12825 ed.render(1); 12826 } else { 12827 each(document.forms, function(f) { 12828 each(f.elements, function(e) { 12829 if (e.name === v) { 12830 v = 'mce_editor_' + instanceCounter++; 12831 DOM.setAttrib(e, 'id', v); 12832 12833 ed = new tinymce.Editor(v, s); 12834 el.push(ed); 12835 ed.render(1); 12836 } 12837 }); 12838 }); 12839 } 12840 }); 12841 } 12842 break; 12843 12844 case "textareas": 12845 case "specific_textareas": 12846 each(DOM.select('textarea'), function(elm) { 12847 if (s.editor_deselector && hasClass(elm, s.editor_deselector)) 12848 return; 12849 12850 if (!s.editor_selector || hasClass(elm, s.editor_selector)) { 12851 ed = new tinymce.Editor(createId(elm), s); 12852 el.push(ed); 12853 ed.render(1); 12854 } 12855 }); 12856 break; 12857 12858 default: 12859 if (s.types) { 12860 // Process type specific selector 12861 each(s.types, function(type) { 12862 each(DOM.select(type.selector), function(elm) { 12863 var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type)); 12864 el.push(editor); 12865 editor.render(1); 12866 }); 12867 }); 12868 } else if (s.selector) { 12869 // Process global selector 12870 each(DOM.select(s.selector), function(elm) { 12871 var editor = new tinymce.Editor(createId(elm), s); 12872 el.push(editor); 12873 editor.render(1); 12874 }); 12875 } 12876 } 12877 12878 // Call onInit when all editors are initialized 12879 if (s.oninit) { 12880 l = co = 0; 12881 12882 each(el, function(ed) { 12883 co++; 12884 12885 if (!ed.initialized) { 12886 // Wait for it 12887 ed.onInit.add(function() { 12888 l++; 12889 12890 // All done 12891 if (l == co) 12892 execCallback(s, 'oninit'); 12893 }); 12894 } else 12895 l++; 12896 12897 // All done 12898 if (l == co) 12899 execCallback(s, 'oninit'); 12900 }); 12901 } 12902 }); 12903 }, 12904 12905 get : function(id) { 12906 if (id === undef) 12907 return this.editors; 12908 12909 return this.editors[id]; 12910 }, 12911 12912 getInstanceById : function(id) { 12913 return this.get(id); 12914 }, 12915 12916 add : function(editor) { 12917 var self = this, editors = self.editors; 12918 12919 // Add named and index editor instance 12920 editors[editor.id] = editor; 12921 editors.push(editor); 12922 12923 self._setActive(editor); 12924 self.onAddEditor.dispatch(self, editor); 12925 12926 12927 return editor; 12928 }, 12929 12930 remove : function(editor) { 12931 var t = this, i, editors = t.editors; 12932 12933 // Not in the collection 12934 if (!editors[editor.id]) 12935 return null; 12936 12937 delete editors[editor.id]; 12938 12939 for (i = 0; i < editors.length; i++) { 12940 if (editors[i] == editor) { 12941 editors.splice(i, 1); 12942 break; 12943 } 12944 } 12945 12946 // Select another editor since the active one was removed 12947 if (t.activeEditor == editor) 12948 t._setActive(editors[0]); 12949 12950 editor.destroy(); 12951 t.onRemoveEditor.dispatch(t, editor); 12952 12953 return editor; 12954 }, 12955 12956 execCommand : function(c, u, v) { 12957 var t = this, ed = t.get(v), w; 12958 12959 function clr() { 12960 ed.destroy(); 12961 w.detachEvent('onunload', clr); 12962 w = w.tinyMCE = w.tinymce = null; // IE leak 12963 }; 12964 12965 // Manager commands 12966 switch (c) { 12967 case "mceFocus": 12968 ed.focus(); 12969 return true; 12970 12971 case "mceAddEditor": 12972 case "mceAddControl": 12973 if (!t.get(v)) 12974 new tinymce.Editor(v, t.settings).render(); 12975 12976 return true; 12977 12978 case "mceAddFrameControl": 12979 w = v.window; 12980 12981 // Add tinyMCE global instance and tinymce namespace to specified window 12982 w.tinyMCE = tinyMCE; 12983 w.tinymce = tinymce; 12984 12985 tinymce.DOM.doc = w.document; 12986 tinymce.DOM.win = w; 12987 12988 ed = new tinymce.Editor(v.element_id, v); 12989 ed.render(); 12990 12991 // Fix IE memory leaks 12992 if (tinymce.isIE) { 12993 w.attachEvent('onunload', clr); 12994 } 12995 12996 v.page_window = null; 12997 12998 return true; 12999 13000 case "mceRemoveEditor": 13001 case "mceRemoveControl": 13002 if (ed) 13003 ed.remove(); 13004 13005 return true; 13006 13007 case 'mceToggleEditor': 13008 if (!ed) { 13009 t.execCommand('mceAddControl', 0, v); 13010 return true; 13011 } 13012 13013 if (ed.isHidden()) 13014 ed.show(); 13015 else 13016 ed.hide(); 13017 13018 return true; 13019 } 13020 13021 // Run command on active editor 13022 if (t.activeEditor) 13023 return t.activeEditor.execCommand(c, u, v); 13024 13025 return false; 13026 }, 13027 13028 execInstanceCommand : function(id, c, u, v) { 13029 var ed = this.get(id); 13030 13031 if (ed) 13032 return ed.execCommand(c, u, v); 13033 13034 return false; 13035 }, 13036 13037 triggerSave : function() { 13038 each(this.editors, function(e) { 13039 e.save(); 13040 }); 13041 }, 13042 13043 addI18n : function(p, o) { 13044 var lo, i18n = this.i18n; 13045 13046 if (!tinymce.is(p, 'string')) { 13047 each(p, function(o, lc) { 13048 each(o, function(o, g) { 13049 each(o, function(o, k) { 13050 if (g === 'common') 13051 i18n[lc + '.' + k] = o; 13052 else 13053 i18n[lc + '.' + g + '.' + k] = o; 13054 }); 13055 }); 13056 }); 13057 } else { 13058 each(o, function(o, k) { 13059 i18n[p + '.' + k] = o; 13060 }); 13061 } 13062 }, 13063 13064 // Private methods 13065 13066 _setActive : function(editor) { 13067 this.selectedInstance = this.activeEditor = editor; 13068 } 13069 }); 13070 })(tinymce); 13071 13072 (function(tinymce) { 13073 // Shorten these names 13074 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, 13075 each = tinymce.each, isGecko = tinymce.isGecko, 13076 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is, 13077 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 13078 explode = tinymce.explode; 13079 13080 tinymce.create('tinymce.Editor', { 13081 Editor : function(id, settings) { 13082 var self = this, TRUE = true; 13083 13084 self.settings = settings = extend({ 13085 id : id, 13086 language : 'en', 13087 theme : 'advanced', 13088 skin : 'default', 13089 delta_width : 0, 13090 delta_height : 0, 13091 popup_css : '', 13092 plugins : '', 13093 document_base_url : tinymce.documentBaseURL, 13094 add_form_submit_trigger : TRUE, 13095 submit_patch : TRUE, 13096 add_unload_trigger : TRUE, 13097 convert_urls : TRUE, 13098 relative_urls : TRUE, 13099 remove_script_host : TRUE, 13100 table_inline_editing : false, 13101 object_resizing : TRUE, 13102 accessibility_focus : TRUE, 13103 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll 13104 visual : TRUE, 13105 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large', 13106 font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size 13107 apply_source_formatting : TRUE, 13108 directionality : 'ltr', 13109 forced_root_block : 'p', 13110 hidden_input : TRUE, 13111 padd_empty_editor : TRUE, 13112 render_ui : TRUE, 13113 indentation : '30px', 13114 fix_table_elements : TRUE, 13115 inline_styles : TRUE, 13116 convert_fonts_to_spans : TRUE, 13117 indent : 'simple', 13118 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 13119 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 13120 validate : TRUE, 13121 entity_encoding : 'named', 13122 url_converter : self.convertURL, 13123 url_converter_scope : self, 13124 ie7_compat : TRUE 13125 }, settings); 13126 13127 self.id = self.editorId = id; 13128 13129 self.isNotDirty = false; 13130 13131 self.plugins = {}; 13132 13133 self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, { 13134 base_uri : tinyMCE.baseURI 13135 }); 13136 13137 self.baseURI = tinymce.baseURI; 13138 13139 self.contentCSS = []; 13140 13141 self.contentStyles = []; 13142 13143 // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic 13144 self.setupEvents(); 13145 13146 // Internal command handler objects 13147 self.execCommands = {}; 13148 self.queryStateCommands = {}; 13149 self.queryValueCommands = {}; 13150 13151 // Call setup 13152 self.execCallback('setup', self); 13153 }, 13154 13155 render : function(nst) { 13156 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader; 13157 13158 // Page is not loaded yet, wait for it 13159 if (!Event.domLoaded) { 13160 Event.add(window, 'ready', function() { 13161 t.render(); 13162 }); 13163 return; 13164 } 13165 13166 tinyMCE.settings = s; 13167 13168 // Element not found, then skip initialization 13169 if (!t.getElement()) 13170 return; 13171 13172 // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 13173 // here since the browser says it has contentEditable support but there is no visible caret. 13174 if (tinymce.isIDevice && !tinymce.isIOS5) 13175 return; 13176 13177 // Add hidden input for non input elements inside form elements 13178 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form')) 13179 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id); 13180 13181 // Hide target element early to prevent content flashing 13182 if (!s.content_editable) { 13183 t.orgVisibility = t.getElement().style.visibility; 13184 t.getElement().style.visibility = 'hidden'; 13185 } 13186 13187 if (tinymce.WindowManager) 13188 t.windowManager = new tinymce.WindowManager(t); 13189 13190 if (s.encoding == 'xml') { 13191 t.onGetContent.add(function(ed, o) { 13192 if (o.save) 13193 o.content = DOM.encode(o.content); 13194 }); 13195 } 13196 13197 if (s.add_form_submit_trigger) { 13198 t.onSubmit.addToTop(function() { 13199 if (t.initialized) { 13200 t.save(); 13201 t.isNotDirty = 1; 13202 } 13203 }); 13204 } 13205 13206 if (s.add_unload_trigger) { 13207 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() { 13208 if (t.initialized && !t.destroyed && !t.isHidden()) 13209 t.save({format : 'raw', no_events : true}); 13210 }); 13211 } 13212 13213 tinymce.addUnload(t.destroy, t); 13214 13215 if (s.submit_patch) { 13216 t.onBeforeRenderUI.add(function() { 13217 var n = t.getElement().form; 13218 13219 if (!n) 13220 return; 13221 13222 // Already patched 13223 if (n._mceOldSubmit) 13224 return; 13225 13226 // Check page uses id="submit" or name="submit" for it's submit button 13227 if (!n.submit.nodeType && !n.submit.length) { 13228 t.formElement = n; 13229 n._mceOldSubmit = n.submit; 13230 n.submit = function() { 13231 // Save all instances 13232 tinymce.triggerSave(); 13233 t.isNotDirty = 1; 13234 13235 return t.formElement._mceOldSubmit(t.formElement); 13236 }; 13237 } 13238 13239 n = null; 13240 }); 13241 } 13242 13243 // Load scripts 13244 function loadScripts() { 13245 if (s.language && s.language_load !== false) 13246 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); 13247 13248 if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) 13249 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); 13250 13251 each(explode(s.plugins), function(p) { 13252 if (p &&!PluginManager.urls[p]) { 13253 if (p.charAt(0) == '-') { 13254 p = p.substr(1, p.length); 13255 var dependencies = PluginManager.dependencies(p); 13256 each(dependencies, function(dep) { 13257 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'}; 13258 dep = PluginManager.createUrl(defaultSettings, dep); 13259 PluginManager.load(dep.resource, dep); 13260 }); 13261 } else { 13262 // Skip safari plugin, since it is removed as of 3.3b1 13263 if (p == 'safari') { 13264 return; 13265 } 13266 PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'}); 13267 } 13268 } 13269 }); 13270 13271 // Init when que is loaded 13272 sl.loadQueue(function() { 13273 if (!t.removed) 13274 t.init(); 13275 }); 13276 }; 13277 13278 loadScripts(); 13279 }, 13280 13281 init : function() { 13282 var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = []; 13283 13284 tinymce.add(t); 13285 13286 s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area')); 13287 13288 if (s.theme) { 13289 if (typeof s.theme != "function") { 13290 s.theme = s.theme.replace(/-/, ''); 13291 o = ThemeManager.get(s.theme); 13292 t.theme = new o(); 13293 13294 if (t.theme.init) 13295 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, '')); 13296 } else { 13297 t.theme = s.theme; 13298 } 13299 } 13300 13301 function initPlugin(p) { 13302 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po; 13303 if (c && tinymce.inArray(initializedPlugins,p) === -1) { 13304 each(PluginManager.dependencies(p), function(dep){ 13305 initPlugin(dep); 13306 }); 13307 po = new c(t, u); 13308 13309 t.plugins[p] = po; 13310 13311 if (po.init) { 13312 po.init(t, u); 13313 initializedPlugins.push(p); 13314 } 13315 } 13316 } 13317 13318 // Create all plugins 13319 each(explode(s.plugins.replace(/\-/g, '')), initPlugin); 13320 13321 // Setup popup CSS path(s) 13322 if (s.popup_css !== false) { 13323 if (s.popup_css) 13324 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css); 13325 else 13326 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css"); 13327 } 13328 13329 if (s.popup_css_add) 13330 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add); 13331 13332 t.controlManager = new tinymce.ControlManager(t); 13333 13334 // Enables users to override the control factory 13335 t.onBeforeRenderUI.dispatch(t, t.controlManager); 13336 13337 // Measure box 13338 if (s.render_ui && t.theme) { 13339 t.orgDisplay = e.style.display; 13340 13341 if (typeof s.theme != "function") { 13342 w = s.width || e.style.width || e.offsetWidth; 13343 h = s.height || e.style.height || e.offsetHeight; 13344 mh = s.min_height || 100; 13345 re = /^[0-9\.]+(|px)$/i; 13346 13347 if (re.test('' + w)) 13348 w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100); 13349 13350 if (re.test('' + h)) 13351 h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh); 13352 13353 // Render UI 13354 o = t.theme.renderUI({ 13355 targetNode : e, 13356 width : w, 13357 height : h, 13358 deltaWidth : s.delta_width, 13359 deltaHeight : s.delta_height 13360 }); 13361 13362 // Resize editor 13363 DOM.setStyles(o.sizeContainer || o.editorContainer, { 13364 width : w, 13365 height : h 13366 }); 13367 13368 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : ''); 13369 if (h < mh) 13370 h = mh; 13371 } else { 13372 o = s.theme(t, e); 13373 13374 // Convert element type to id:s 13375 if (o.editorContainer.nodeType) { 13376 o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent"; 13377 } 13378 13379 // Convert element type to id:s 13380 if (o.iframeContainer.nodeType) { 13381 o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer"; 13382 } 13383 13384 // Use specified iframe height or the targets offsetHeight 13385 h = o.iframeHeight || e.offsetHeight; 13386 13387 // Store away the selection when it's changed to it can be restored later with a editor.focus() call 13388 if (isIE) { 13389 t.onInit.add(function(ed) { 13390 ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() { 13391 ed.lastIERng = ed.selection.getRng(); 13392 }); 13393 }); 13394 } 13395 } 13396 13397 t.editorContainer = o.editorContainer; 13398 } 13399 13400 // Load specified content CSS last 13401 if (s.content_css) { 13402 each(explode(s.content_css), function(u) { 13403 t.contentCSS.push(t.documentBaseURI.toAbsolute(u)); 13404 }); 13405 } 13406 13407 // Content editable mode ends here 13408 if (s.content_editable) { 13409 e = n = o = null; // Fix IE leak 13410 return t.initContentBody(); 13411 } 13412 13413 // User specified a document.domain value 13414 if (document.domain && location.hostname != document.domain) 13415 tinymce.relaxedDomain = document.domain; 13416 13417 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">'; 13418 13419 // We only need to override paths if we have to 13420 // IE has a bug where it remove site absolute urls to relative ones if this is specified 13421 if (s.document_base_url != tinymce.documentBaseURL) 13422 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />'; 13423 13424 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. 13425 if (s.ie7_compat) 13426 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />'; 13427 else 13428 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'; 13429 13430 t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'; 13431 13432 // Load the CSS by injecting them into the HTML this will reduce "flicker" 13433 for (i = 0; i < t.contentCSS.length; i++) { 13434 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />'; 13435 } 13436 13437 t.contentCSS = []; 13438 13439 bi = s.body_id || 'tinymce'; 13440 if (bi.indexOf('=') != -1) { 13441 bi = t.getParam('body_id', '', 'hash'); 13442 bi = bi[t.id] || bi; 13443 } 13444 13445 bc = s.body_class || ''; 13446 if (bc.indexOf('=') != -1) { 13447 bc = t.getParam('body_class', '', 'hash'); 13448 bc = bc[t.id] || ''; 13449 } 13450 13451 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>'; 13452 13453 // Domain relaxing enabled, then set document domain 13454 if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) { 13455 // We need to write the contents here in IE since multiple writes messes up refresh button and back button 13456 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()'; 13457 } 13458 13459 // Create iframe 13460 // TODO: ACC add the appropriate description on this. 13461 n = DOM.add(o.iframeContainer, 'iframe', { 13462 id : t.id + "_ifr", 13463 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 13464 frameBorder : '0', 13465 allowTransparency : "true", 13466 title : s.aria_label, 13467 style : { 13468 width : '100%', 13469 height : h, 13470 display : 'block' // Important for Gecko to render the iframe correctly 13471 } 13472 }); 13473 13474 t.contentAreaContainer = o.iframeContainer; 13475 13476 if (o.editorContainer) { 13477 DOM.get(o.editorContainer).style.display = t.orgDisplay; 13478 } 13479 13480 // Restore visibility on target element 13481 e.style.visibility = t.orgVisibility; 13482 13483 DOM.get(t.id).style.display = 'none'; 13484 DOM.setAttrib(t.id, 'aria-hidden', true); 13485 13486 if (!tinymce.relaxedDomain || !u) 13487 t.initContentBody(); 13488 13489 e = n = o = null; // Cleanup 13490 }, 13491 13492 initContentBody : function() { 13493 var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText; 13494 13495 // Setup iframe body 13496 if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) { 13497 doc.open(); 13498 doc.write(self.iframeHTML); 13499 doc.close(); 13500 13501 if (tinymce.relaxedDomain) 13502 doc.domain = tinymce.relaxedDomain; 13503 } 13504 13505 if (settings.content_editable) { 13506 DOM.addClass(targetElm, 'mceContentBody'); 13507 self.contentDocument = doc = settings.content_document || document; 13508 self.contentWindow = settings.content_window || window; 13509 self.bodyElement = targetElm; 13510 13511 // Prevent leak in IE 13512 settings.content_document = settings.content_window = null; 13513 } 13514 13515 // It will not steal focus while setting contentEditable 13516 body = self.getBody(); 13517 body.disabled = true; 13518 13519 if (!settings.readonly) 13520 body.contentEditable = self.getParam('content_editable_state', true); 13521 13522 body.disabled = false; 13523 13524 self.schema = new tinymce.html.Schema(settings); 13525 13526 self.dom = new tinymce.dom.DOMUtils(doc, { 13527 keep_values : true, 13528 url_converter : self.convertURL, 13529 url_converter_scope : self, 13530 hex_colors : settings.force_hex_style_colors, 13531 class_filter : settings.class_filter, 13532 update_styles : true, 13533 root_element : settings.content_editable ? self.id : null, 13534 schema : self.schema 13535 }); 13536 13537 self.parser = new tinymce.html.DomParser(settings, self.schema); 13538 13539 // Convert src and href into data-mce-src, data-mce-href and data-mce-style 13540 self.parser.addAttributeFilter('src,href,style', function(nodes, name) { 13541 var i = nodes.length, node, dom = self.dom, value, internalName; 13542 13543 while (i--) { 13544 node = nodes[i]; 13545 value = node.attr(name); 13546 internalName = 'data-mce-' + name; 13547 13548 // Add internal attribute if we need to we don't on a refresh of the document 13549 if (!node.attributes.map[internalName]) { 13550 if (name === "style") 13551 node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name)); 13552 else 13553 node.attr(internalName, self.convertURL(value, name, node.name)); 13554 } 13555 } 13556 }); 13557 13558 // Keep scripts from executing 13559 self.parser.addNodeFilter('script', function(nodes, name) { 13560 var i = nodes.length, node; 13561 13562 while (i--) { 13563 node = nodes[i]; 13564 node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript')); 13565 } 13566 }); 13567 13568 self.parser.addNodeFilter('#cdata', function(nodes, name) { 13569 var i = nodes.length, node; 13570 13571 while (i--) { 13572 node = nodes[i]; 13573 node.type = 8; 13574 node.name = '#comment'; 13575 node.value = '[CDATA[' + node.value + ']]'; 13576 } 13577 }); 13578 13579 self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) { 13580 var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements(); 13581 13582 while (i--) { 13583 node = nodes[i]; 13584 13585 if (node.isEmpty(nonEmptyElements)) 13586 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true; 13587 } 13588 }); 13589 13590 self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema); 13591 13592 self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self); 13593 13594 self.formatter = new tinymce.Formatter(self); 13595 13596 self.undoManager = new tinymce.UndoManager(self); 13597 13598 self.forceBlocks = new tinymce.ForceBlocks(self); 13599 self.enterKey = new tinymce.EnterKey(self); 13600 self.editorCommands = new tinymce.EditorCommands(self); 13601 13602 self.onExecCommand.add(function(editor, command) { 13603 // Don't refresh the select lists until caret move 13604 if (!/^(FontName|FontSize)$/.test(command)) 13605 self.nodeChanged(); 13606 }); 13607 13608 // Pass through 13609 self.serializer.onPreProcess.add(function(se, o) { 13610 return self.onPreProcess.dispatch(self, o, se); 13611 }); 13612 13613 self.serializer.onPostProcess.add(function(se, o) { 13614 return self.onPostProcess.dispatch(self, o, se); 13615 }); 13616 13617 self.onPreInit.dispatch(self); 13618 13619 if (!settings.browser_spellcheck && !settings.gecko_spellcheck) 13620 doc.body.spellcheck = false; 13621 13622 if (!settings.readonly) { 13623 self.bindNativeEvents(); 13624 } 13625 13626 self.controlManager.onPostRender.dispatch(self, self.controlManager); 13627 self.onPostRender.dispatch(self); 13628 13629 self.quirks = tinymce.util.Quirks(self); 13630 13631 if (settings.directionality) 13632 body.dir = settings.directionality; 13633 13634 if (settings.nowrap) 13635 body.style.whiteSpace = "nowrap"; 13636 13637 if (settings.protect) { 13638 self.onBeforeSetContent.add(function(ed, o) { 13639 each(settings.protect, function(pattern) { 13640 o.content = o.content.replace(pattern, function(str) { 13641 return '<!--mce:protected ' + escape(str) + '-->'; 13642 }); 13643 }); 13644 }); 13645 } 13646 13647 // Add visual aids when new contents is added 13648 self.onSetContent.add(function() { 13649 self.addVisual(self.getBody()); 13650 }); 13651 13652 // Remove empty contents 13653 if (settings.padd_empty_editor) { 13654 self.onPostProcess.add(function(ed, o) { 13655 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, ''); 13656 }); 13657 } 13658 13659 self.load({initial : true, format : 'html'}); 13660 self.startContent = self.getContent({format : 'raw'}); 13661 13662 self.initialized = true; 13663 13664 self.onInit.dispatch(self); 13665 self.execCallback('setupcontent_callback', self.id, body, doc); 13666 self.execCallback('init_instance_callback', self); 13667 self.focus(true); 13668 self.nodeChanged({initial : true}); 13669 13670 // Add editor specific CSS styles 13671 if (self.contentStyles.length > 0) { 13672 contentCssText = ''; 13673 13674 each(self.contentStyles, function(style) { 13675 contentCssText += style + "\r\n"; 13676 }); 13677 13678 self.dom.addStyle(contentCssText); 13679 } 13680 13681 // Load specified content CSS last 13682 each(self.contentCSS, function(url) { 13683 self.dom.loadCSS(url); 13684 }); 13685 13686 // Handle auto focus 13687 if (settings.auto_focus) { 13688 setTimeout(function () { 13689 var ed = tinymce.get(settings.auto_focus); 13690 13691 ed.selection.select(ed.getBody(), 1); 13692 ed.selection.collapse(1); 13693 ed.getBody().focus(); 13694 ed.getWin().focus(); 13695 }, 100); 13696 } 13697 13698 // Clean up references for IE 13699 targetElm = doc = body = null; 13700 }, 13701 13702 focus : function(skip_focus) { 13703 var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body; 13704 13705 if (!skip_focus) { 13706 if (self.lastIERng) { 13707 selection.setRng(self.lastIERng); 13708 } 13709 13710 // Get selected control element 13711 ieRng = selection.getRng(); 13712 if (ieRng.item) { 13713 controlElm = ieRng.item(0); 13714 } 13715 13716 self._refreshContentEditable(); 13717 13718 // Focus the window iframe 13719 if (!contentEditable) { 13720 self.getWin().focus(); 13721 } 13722 13723 // Focus the body as well since it's contentEditable 13724 if (tinymce.isGecko || contentEditable) { 13725 body = self.getBody(); 13726 13727 // Check for setActive since it doesn't scroll to the element 13728 if (body.setActive) { 13729 body.setActive(); 13730 } else { 13731 body.focus(); 13732 } 13733 13734 if (contentEditable) { 13735 selection.normalize(); 13736 } 13737 } 13738 13739 // Restore selected control element 13740 // This is needed when for example an image is selected within a 13741 // layer a call to focus will then remove the control selection 13742 if (controlElm && controlElm.ownerDocument == doc) { 13743 ieRng = doc.body.createControlRange(); 13744 ieRng.addElement(controlElm); 13745 ieRng.select(); 13746 } 13747 } 13748 13749 if (tinymce.activeEditor != self) { 13750 if ((oed = tinymce.activeEditor) != null) 13751 oed.onDeactivate.dispatch(oed, self); 13752 13753 self.onActivate.dispatch(self, oed); 13754 } 13755 13756 tinymce._setActive(self); 13757 }, 13758 13759 execCallback : function(n) { 13760 var t = this, f = t.settings[n], s; 13761 13762 if (!f) 13763 return; 13764 13765 // Look through lookup 13766 if (t.callbackLookup && (s = t.callbackLookup[n])) { 13767 f = s.func; 13768 s = s.scope; 13769 } 13770 13771 if (is(f, 'string')) { 13772 s = f.replace(/\.\w+$/, ''); 13773 s = s ? tinymce.resolve(s) : 0; 13774 f = tinymce.resolve(f); 13775 t.callbackLookup = t.callbackLookup || {}; 13776 t.callbackLookup[n] = {func : f, scope : s}; 13777 } 13778 13779 return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); 13780 }, 13781 13782 translate : function(s) { 13783 var c = this.settings.language || 'en', i18n = tinymce.i18n; 13784 13785 if (!s) 13786 return ''; 13787 13788 return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) { 13789 return i18n[c + '.' + b] || '{#' + b + '}'; 13790 }); 13791 }, 13792 13793 getLang : function(n, dv) { 13794 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); 13795 }, 13796 13797 getParam : function(n, dv, ty) { 13798 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; 13799 13800 if (ty === 'hash') { 13801 o = {}; 13802 13803 if (is(v, 'string')) { 13804 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { 13805 v = v.split('='); 13806 13807 if (v.length > 1) 13808 o[tr(v[0])] = tr(v[1]); 13809 else 13810 o[tr(v[0])] = tr(v); 13811 }); 13812 } else 13813 o = v; 13814 13815 return o; 13816 } 13817 13818 return v; 13819 }, 13820 13821 nodeChanged : function(o) { 13822 var self = this, selection = self.selection, node; 13823 13824 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading 13825 if (self.initialized) { 13826 o = o || {}; 13827 13828 // Get start node 13829 node = selection.getStart() || self.getBody(); 13830 node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state 13831 13832 // Get parents and add them to object 13833 o.parents = []; 13834 self.dom.getParent(node, function(node) { 13835 if (node.nodeName == 'BODY') 13836 return true; 13837 13838 o.parents.push(node); 13839 }); 13840 13841 self.onNodeChange.dispatch( 13842 self, 13843 o ? o.controlManager || self.controlManager : self.controlManager, 13844 node, 13845 selection.isCollapsed(), 13846 o 13847 ); 13848 } 13849 }, 13850 13851 addButton : function(name, settings) { 13852 var self = this; 13853 13854 self.buttons = self.buttons || {}; 13855 self.buttons[name] = settings; 13856 }, 13857 13858 addCommand : function(name, callback, scope) { 13859 this.execCommands[name] = {func : callback, scope : scope || this}; 13860 }, 13861 13862 addQueryStateHandler : function(name, callback, scope) { 13863 this.queryStateCommands[name] = {func : callback, scope : scope || this}; 13864 }, 13865 13866 addQueryValueHandler : function(name, callback, scope) { 13867 this.queryValueCommands[name] = {func : callback, scope : scope || this}; 13868 }, 13869 13870 addShortcut : function(pa, desc, cmd_func, sc) { 13871 var t = this, c; 13872 13873 if (t.settings.custom_shortcuts === false) 13874 return false; 13875 13876 t.shortcuts = t.shortcuts || {}; 13877 13878 if (is(cmd_func, 'string')) { 13879 c = cmd_func; 13880 13881 cmd_func = function() { 13882 t.execCommand(c, false, null); 13883 }; 13884 } 13885 13886 if (is(cmd_func, 'object')) { 13887 c = cmd_func; 13888 13889 cmd_func = function() { 13890 t.execCommand(c[0], c[1], c[2]); 13891 }; 13892 } 13893 13894 each(explode(pa), function(pa) { 13895 var o = { 13896 func : cmd_func, 13897 scope : sc || this, 13898 desc : t.translate(desc), 13899 alt : false, 13900 ctrl : false, 13901 shift : false 13902 }; 13903 13904 each(explode(pa, '+'), function(v) { 13905 switch (v) { 13906 case 'alt': 13907 case 'ctrl': 13908 case 'shift': 13909 o[v] = true; 13910 break; 13911 13912 default: 13913 o.charCode = v.charCodeAt(0); 13914 o.keyCode = v.toUpperCase().charCodeAt(0); 13915 } 13916 }); 13917 13918 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; 13919 }); 13920 13921 return true; 13922 }, 13923 13924 execCommand : function(cmd, ui, val, a) { 13925 var t = this, s = 0, o, st; 13926 13927 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) 13928 t.focus(); 13929 13930 a = extend({}, a); 13931 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a); 13932 if (a.terminate) 13933 return false; 13934 13935 // Command callback 13936 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { 13937 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13938 return true; 13939 } 13940 13941 // Registred commands 13942 if (o = t.execCommands[cmd]) { 13943 st = o.func.call(o.scope, ui, val); 13944 13945 // Fall through on true 13946 if (st !== true) { 13947 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13948 return st; 13949 } 13950 } 13951 13952 // Plugin commands 13953 each(t.plugins, function(p) { 13954 if (p.execCommand && p.execCommand(cmd, ui, val)) { 13955 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13956 s = 1; 13957 return false; 13958 } 13959 }); 13960 13961 if (s) 13962 return true; 13963 13964 // Theme commands 13965 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { 13966 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13967 return true; 13968 } 13969 13970 // Editor commands 13971 if (t.editorCommands.execCommand(cmd, ui, val)) { 13972 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13973 return true; 13974 } 13975 13976 // Browser commands 13977 t.getDoc().execCommand(cmd, ui, val); 13978 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13979 }, 13980 13981 queryCommandState : function(cmd) { 13982 var t = this, o, s; 13983 13984 // Is hidden then return undefined 13985 if (t._isHidden()) 13986 return; 13987 13988 // Registred commands 13989 if (o = t.queryStateCommands[cmd]) { 13990 s = o.func.call(o.scope); 13991 13992 // Fall though on true 13993 if (s !== true) 13994 return s; 13995 } 13996 13997 // Registred commands 13998 o = t.editorCommands.queryCommandState(cmd); 13999 if (o !== -1) 14000 return o; 14001 14002 // Browser commands 14003 try { 14004 return this.getDoc().queryCommandState(cmd); 14005 } catch (ex) { 14006 // Fails sometimes see bug: 1896577 14007 } 14008 }, 14009 14010 queryCommandValue : function(c) { 14011 var t = this, o, s; 14012 14013 // Is hidden then return undefined 14014 if (t._isHidden()) 14015 return; 14016 14017 // Registred commands 14018 if (o = t.queryValueCommands[c]) { 14019 s = o.func.call(o.scope); 14020 14021 // Fall though on true 14022 if (s !== true) 14023 return s; 14024 } 14025 14026 // Registred commands 14027 o = t.editorCommands.queryCommandValue(c); 14028 if (is(o)) 14029 return o; 14030 14031 // Browser commands 14032 try { 14033 return this.getDoc().queryCommandValue(c); 14034 } catch (ex) { 14035 // Fails sometimes see bug: 1896577 14036 } 14037 }, 14038 14039 show : function() { 14040 var self = this; 14041 14042 DOM.show(self.getContainer()); 14043 DOM.hide(self.id); 14044 self.load(); 14045 }, 14046 14047 hide : function() { 14048 var self = this, doc = self.getDoc(); 14049 14050 // Fixed bug where IE has a blinking cursor left from the editor 14051 if (isIE && doc) 14052 doc.execCommand('SelectAll'); 14053 14054 // We must save before we hide so Safari doesn't crash 14055 self.save(); 14056 DOM.hide(self.getContainer()); 14057 DOM.setStyle(self.id, 'display', self.orgDisplay); 14058 }, 14059 14060 isHidden : function() { 14061 return !DOM.isHidden(this.id); 14062 }, 14063 14064 setProgressState : function(b, ti, o) { 14065 this.onSetProgressState.dispatch(this, b, ti, o); 14066 14067 return b; 14068 }, 14069 14070 load : function(o) { 14071 var t = this, e = t.getElement(), h; 14072 14073 if (e) { 14074 o = o || {}; 14075 o.load = true; 14076 14077 // Double encode existing entities in the value 14078 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); 14079 o.element = e; 14080 14081 if (!o.no_events) 14082 t.onLoadContent.dispatch(t, o); 14083 14084 o.element = e = null; 14085 14086 return h; 14087 } 14088 }, 14089 14090 save : function(o) { 14091 var t = this, e = t.getElement(), h, f; 14092 14093 if (!e || !t.initialized) 14094 return; 14095 14096 o = o || {}; 14097 o.save = true; 14098 14099 o.element = e; 14100 h = o.content = t.getContent(o); 14101 14102 if (!o.no_events) 14103 t.onSaveContent.dispatch(t, o); 14104 14105 h = o.content; 14106 14107 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { 14108 e.innerHTML = h; 14109 14110 // Update hidden form element 14111 if (f = DOM.getParent(t.id, 'form')) { 14112 each(f.elements, function(e) { 14113 if (e.name == t.id) { 14114 e.value = h; 14115 return false; 14116 } 14117 }); 14118 } 14119 } else 14120 e.value = h; 14121 14122 o.element = e = null; 14123 14124 return h; 14125 }, 14126 14127 setContent : function(content, args) { 14128 var self = this, rootNode, body = self.getBody(), forcedRootBlockName; 14129 14130 // Setup args object 14131 args = args || {}; 14132 args.format = args.format || 'html'; 14133 args.set = true; 14134 args.content = content; 14135 14136 // Do preprocessing 14137 if (!args.no_events) 14138 self.onBeforeSetContent.dispatch(self, args); 14139 14140 content = args.content; 14141 14142 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content 14143 // It will also be impossible to place the caret in the editor unless there is a BR element present 14144 if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) { 14145 forcedRootBlockName = self.settings.forced_root_block; 14146 if (forcedRootBlockName) 14147 content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>'; 14148 else 14149 content = '<br data-mce-bogus="1">'; 14150 14151 body.innerHTML = content; 14152 self.selection.select(body, true); 14153 self.selection.collapse(true); 14154 return; 14155 } 14156 14157 // Parse and serialize the html 14158 if (args.format !== 'raw') { 14159 content = new tinymce.html.Serializer({}, self.schema).serialize( 14160 self.parser.parse(content) 14161 ); 14162 } 14163 14164 // Set the new cleaned contents to the editor 14165 args.content = tinymce.trim(content); 14166 self.dom.setHTML(body, args.content); 14167 14168 // Do post processing 14169 if (!args.no_events) 14170 self.onSetContent.dispatch(self, args); 14171 14172 // Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise 14173 if (!self.settings.content_editable || document.activeElement === self.getBody()) { 14174 self.selection.normalize(); 14175 } 14176 14177 return args.content; 14178 }, 14179 14180 getContent : function(args) { 14181 var self = this, content; 14182 14183 // Setup args object 14184 args = args || {}; 14185 args.format = args.format || 'html'; 14186 args.get = true; 14187 args.getInner = true; 14188 14189 // Do preprocessing 14190 if (!args.no_events) 14191 self.onBeforeGetContent.dispatch(self, args); 14192 14193 // Get raw contents or by default the cleaned contents 14194 if (args.format == 'raw') 14195 content = self.getBody().innerHTML; 14196 else 14197 content = self.serializer.serialize(self.getBody(), args); 14198 14199 args.content = tinymce.trim(content); 14200 14201 // Do post processing 14202 if (!args.no_events) 14203 self.onGetContent.dispatch(self, args); 14204 14205 return args.content; 14206 }, 14207 14208 isDirty : function() { 14209 var self = this; 14210 14211 return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty; 14212 }, 14213 14214 getContainer : function() { 14215 var self = this; 14216 14217 if (!self.container) 14218 self.container = DOM.get(self.editorContainer || self.id + '_parent'); 14219 14220 return self.container; 14221 }, 14222 14223 getContentAreaContainer : function() { 14224 return this.contentAreaContainer; 14225 }, 14226 14227 getElement : function() { 14228 return DOM.get(this.settings.content_element || this.id); 14229 }, 14230 14231 getWin : function() { 14232 var self = this, elm; 14233 14234 if (!self.contentWindow) { 14235 elm = DOM.get(self.id + "_ifr"); 14236 14237 if (elm) 14238 self.contentWindow = elm.contentWindow; 14239 } 14240 14241 return self.contentWindow; 14242 }, 14243 14244 getDoc : function() { 14245 var self = this, win; 14246 14247 if (!self.contentDocument) { 14248 win = self.getWin(); 14249 14250 if (win) 14251 self.contentDocument = win.document; 14252 } 14253 14254 return self.contentDocument; 14255 }, 14256 14257 getBody : function() { 14258 return this.bodyElement || this.getDoc().body; 14259 }, 14260 14261 convertURL : function(url, name, elm) { 14262 var self = this, settings = self.settings; 14263 14264 // Use callback instead 14265 if (settings.urlconverter_callback) 14266 return self.execCallback('urlconverter_callback', url, elm, true, name); 14267 14268 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs 14269 if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0) 14270 return url; 14271 14272 // Convert to relative 14273 if (settings.relative_urls) 14274 return self.documentBaseURI.toRelative(url); 14275 14276 // Convert to absolute 14277 url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host); 14278 14279 return url; 14280 }, 14281 14282 addVisual : function(elm) { 14283 var self = this, settings = self.settings, dom = self.dom, cls; 14284 14285 elm = elm || self.getBody(); 14286 14287 if (!is(self.hasVisual)) 14288 self.hasVisual = settings.visual; 14289 14290 each(dom.select('table,a', elm), function(elm) { 14291 var value; 14292 14293 switch (elm.nodeName) { 14294 case 'TABLE': 14295 cls = settings.visual_table_class || 'mceItemTable'; 14296 value = dom.getAttrib(elm, 'border'); 14297 14298 if (!value || value == '0') { 14299 if (self.hasVisual) 14300 dom.addClass(elm, cls); 14301 else 14302 dom.removeClass(elm, cls); 14303 } 14304 14305 return; 14306 14307 case 'A': 14308 if (!dom.getAttrib(elm, 'href', false)) { 14309 value = dom.getAttrib(elm, 'name') || elm.id; 14310 cls = 'mceItemAnchor'; 14311 14312 if (value) { 14313 if (self.hasVisual) 14314 dom.addClass(elm, cls); 14315 else 14316 dom.removeClass(elm, cls); 14317 } 14318 } 14319 14320 return; 14321 } 14322 }); 14323 14324 self.onVisualAid.dispatch(self, elm, self.hasVisual); 14325 }, 14326 14327 remove : function() { 14328 var self = this, elm = self.getContainer(); 14329 14330 if (!self.removed) { 14331 self.removed = 1; // Cancels post remove event execution 14332 self.hide(); 14333 14334 // Don't clear the window or document if content editable 14335 // is enabled since other instances might still be present 14336 if (!self.settings.content_editable) { 14337 Event.unbind(self.getWin()); 14338 Event.unbind(self.getDoc()); 14339 } 14340 14341 Event.unbind(self.getBody()); 14342 Event.clear(elm); 14343 14344 self.execCallback('remove_instance_callback', self); 14345 self.onRemove.dispatch(self); 14346 14347 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command 14348 self.onExecCommand.listeners = []; 14349 14350 tinymce.remove(self); 14351 DOM.remove(elm); 14352 } 14353 }, 14354 14355 destroy : function(s) { 14356 var t = this; 14357 14358 // One time is enough 14359 if (t.destroyed) 14360 return; 14361 14362 // We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message 14363 if (isGecko) { 14364 Event.unbind(t.getDoc()); 14365 Event.unbind(t.getWin()); 14366 Event.unbind(t.getBody()); 14367 } 14368 14369 if (!s) { 14370 tinymce.removeUnload(t.destroy); 14371 tinyMCE.onBeforeUnload.remove(t._beforeUnload); 14372 14373 // Manual destroy 14374 if (t.theme && t.theme.destroy) 14375 t.theme.destroy(); 14376 14377 // Destroy controls, selection and dom 14378 t.controlManager.destroy(); 14379 t.selection.destroy(); 14380 t.dom.destroy(); 14381 } 14382 14383 if (t.formElement) { 14384 t.formElement.submit = t.formElement._mceOldSubmit; 14385 t.formElement._mceOldSubmit = null; 14386 } 14387 14388 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; 14389 14390 if (t.selection) 14391 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; 14392 14393 t.destroyed = 1; 14394 }, 14395 14396 // Internal functions 14397 14398 _refreshContentEditable : function() { 14399 var self = this, body, parent; 14400 14401 // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again 14402 if (self._isHidden()) { 14403 body = self.getBody(); 14404 parent = body.parentNode; 14405 14406 parent.removeChild(body); 14407 parent.appendChild(body); 14408 14409 body.focus(); 14410 } 14411 }, 14412 14413 _isHidden : function() { 14414 var s; 14415 14416 if (!isGecko) 14417 return 0; 14418 14419 // Weird, wheres that cursor selection? 14420 s = this.selection.getSel(); 14421 return (!s || !s.rangeCount || s.rangeCount === 0); 14422 } 14423 }); 14424 })(tinymce); 14425 (function(tinymce) { 14426 var each = tinymce.each; 14427 14428 tinymce.Editor.prototype.setupEvents = function() { 14429 var self = this, settings = self.settings; 14430 14431 // Add events to the editor 14432 each([ 14433 'onPreInit', 14434 14435 'onBeforeRenderUI', 14436 14437 'onPostRender', 14438 14439 'onLoad', 14440 14441 'onInit', 14442 14443 'onRemove', 14444 14445 'onActivate', 14446 14447 'onDeactivate', 14448 14449 'onClick', 14450 14451 'onEvent', 14452 14453 'onMouseUp', 14454 14455 'onMouseDown', 14456 14457 'onDblClick', 14458 14459 'onKeyDown', 14460 14461 'onKeyUp', 14462 14463 'onKeyPress', 14464 14465 'onContextMenu', 14466 14467 'onSubmit', 14468 14469 'onReset', 14470 14471 'onPaste', 14472 14473 'onPreProcess', 14474 14475 'onPostProcess', 14476 14477 'onBeforeSetContent', 14478 14479 'onBeforeGetContent', 14480 14481 'onSetContent', 14482 14483 'onGetContent', 14484 14485 'onLoadContent', 14486 14487 'onSaveContent', 14488 14489 'onNodeChange', 14490 14491 'onChange', 14492 14493 'onBeforeExecCommand', 14494 14495 'onExecCommand', 14496 14497 'onUndo', 14498 14499 'onRedo', 14500 14501 'onVisualAid', 14502 14503 'onSetProgressState', 14504 14505 'onSetAttrib' 14506 ], function(name) { 14507 self[name] = new tinymce.util.Dispatcher(self); 14508 }); 14509 14510 // Handle legacy cleanup_callback option 14511 if (settings.cleanup_callback) { 14512 self.onBeforeSetContent.add(function(ed, o) { 14513 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14514 }); 14515 14516 self.onPreProcess.add(function(ed, o) { 14517 if (o.set) 14518 ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); 14519 14520 if (o.get) 14521 ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); 14522 }); 14523 14524 self.onPostProcess.add(function(ed, o) { 14525 if (o.set) 14526 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14527 14528 if (o.get) 14529 o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o); 14530 }); 14531 } 14532 14533 // Handle legacy save_callback option 14534 if (settings.save_callback) { 14535 self.onGetContent.add(function(ed, o) { 14536 if (o.save) 14537 o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14538 }); 14539 } 14540 14541 // Handle legacy handle_event_callback option 14542 if (settings.handle_event_callback) { 14543 self.onEvent.add(function(ed, e, o) { 14544 if (self.execCallback('handle_event_callback', e, ed, o) === false) { 14545 e.preventDefault(); 14546 e.stopPropagation(); 14547 } 14548 }); 14549 } 14550 14551 // Handle legacy handle_node_change_callback option 14552 if (settings.handle_node_change_callback) { 14553 self.onNodeChange.add(function(ed, cm, n) { 14554 ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed()); 14555 }); 14556 } 14557 14558 // Handle legacy save_callback option 14559 if (settings.save_callback) { 14560 self.onSaveContent.add(function(ed, o) { 14561 var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14562 14563 if (h) 14564 o.content = h; 14565 }); 14566 } 14567 14568 // Handle legacy onchange_callback option 14569 if (settings.onchange_callback) { 14570 self.onChange.add(function(ed, l) { 14571 ed.execCallback('onchange_callback', ed, l); 14572 }); 14573 } 14574 }; 14575 14576 tinymce.Editor.prototype.bindNativeEvents = function() { 14577 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset 14578 var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap; 14579 14580 nativeToDispatcherMap = { 14581 mouseup : 'onMouseUp', 14582 mousedown : 'onMouseDown', 14583 click : 'onClick', 14584 keyup : 'onKeyUp', 14585 keydown : 'onKeyDown', 14586 keypress : 'onKeyPress', 14587 submit : 'onSubmit', 14588 reset : 'onReset', 14589 contextmenu : 'onContextMenu', 14590 dblclick : 'onDblClick', 14591 paste : 'onPaste' // Doesn't work in all browsers yet 14592 }; 14593 14594 // Handler that takes a native event and sends it out to a dispatcher like onKeyDown 14595 function eventHandler(evt, args) { 14596 var type = evt.type; 14597 14598 // Don't fire events when it's removed 14599 if (self.removed) 14600 return; 14601 14602 // Sends the native event out to a global dispatcher then to the specific event dispatcher 14603 if (self.onEvent.dispatch(self, evt, args) !== false) { 14604 self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args); 14605 } 14606 }; 14607 14608 // Opera doesn't support focus event for contentEditable elements so we need to fake it 14609 function doOperaFocus(e) { 14610 self.focus(true); 14611 }; 14612 14613 function nodeChanged(ed, e) { 14614 // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything 14615 if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) { 14616 self.selection.normalize(); 14617 } 14618 14619 self.nodeChanged(); 14620 } 14621 14622 // Add DOM events 14623 each(nativeToDispatcherMap, function(dispatcherName, nativeName) { 14624 var root = settings.content_editable ? self.getBody() : self.getDoc(); 14625 14626 switch (nativeName) { 14627 case 'contextmenu': 14628 dom.bind(root, nativeName, eventHandler); 14629 break; 14630 14631 case 'paste': 14632 dom.bind(self.getBody(), nativeName, eventHandler); 14633 break; 14634 14635 case 'submit': 14636 case 'reset': 14637 dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler); 14638 break; 14639 14640 default: 14641 dom.bind(root, nativeName, eventHandler); 14642 } 14643 }); 14644 14645 // Set the editor as active when focused 14646 dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) { 14647 self.focus(true); 14648 }); 14649 14650 if (settings.content_editable && tinymce.isOpera) { 14651 dom.bind(self.getBody(), 'click', doOperaFocus); 14652 dom.bind(self.getBody(), 'keydown', doOperaFocus); 14653 } 14654 14655 // Add node change handler 14656 self.onMouseUp.add(nodeChanged); 14657 14658 self.onKeyUp.add(function(ed, e) { 14659 var keyCode = e.keyCode; 14660 14661 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey) 14662 nodeChanged(ed, e); 14663 }); 14664 14665 // Add reset handler 14666 self.onReset.add(function() { 14667 self.setContent(self.startContent, {format : 'raw'}); 14668 }); 14669 14670 // Add shortcuts 14671 function handleShortcut(e, execute) { 14672 if (e.altKey || e.ctrlKey || e.metaKey) { 14673 each(self.shortcuts, function(shortcut) { 14674 var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey; 14675 14676 if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) 14677 return; 14678 14679 if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) { 14680 e.preventDefault(); 14681 14682 if (execute) { 14683 shortcut.func.call(shortcut.scope); 14684 } 14685 14686 return true; 14687 } 14688 }); 14689 } 14690 }; 14691 14692 self.onKeyUp.add(function(ed, e) { 14693 handleShortcut(e); 14694 }); 14695 14696 self.onKeyPress.add(function(ed, e) { 14697 handleShortcut(e); 14698 }); 14699 14700 self.onKeyDown.add(function(ed, e) { 14701 handleShortcut(e, true); 14702 }); 14703 14704 if (tinymce.isOpera) { 14705 self.onClick.add(function(ed, e) { 14706 e.preventDefault(); 14707 }); 14708 } 14709 }; 14710 })(tinymce); 14711 (function(tinymce) { 14712 // Added for compression purposes 14713 var each = tinymce.each, undef, TRUE = true, FALSE = false; 14714 14715 tinymce.EditorCommands = function(editor) { 14716 var dom = editor.dom, 14717 selection = editor.selection, 14718 commands = {state: {}, exec : {}, value : {}}, 14719 settings = editor.settings, 14720 formatter = editor.formatter, 14721 bookmark; 14722 14723 function execCommand(command, ui, value) { 14724 var func; 14725 14726 command = command.toLowerCase(); 14727 if (func = commands.exec[command]) { 14728 func(command, ui, value); 14729 return TRUE; 14730 } 14731 14732 return FALSE; 14733 }; 14734 14735 function queryCommandState(command) { 14736 var func; 14737 14738 command = command.toLowerCase(); 14739 if (func = commands.state[command]) 14740 return func(command); 14741 14742 return -1; 14743 }; 14744 14745 function queryCommandValue(command) { 14746 var func; 14747 14748 command = command.toLowerCase(); 14749 if (func = commands.value[command]) 14750 return func(command); 14751 14752 return FALSE; 14753 }; 14754 14755 function addCommands(command_list, type) { 14756 type = type || 'exec'; 14757 14758 each(command_list, function(callback, command) { 14759 each(command.toLowerCase().split(','), function(command) { 14760 commands[type][command] = callback; 14761 }); 14762 }); 14763 }; 14764 14765 // Expose public methods 14766 tinymce.extend(this, { 14767 execCommand : execCommand, 14768 queryCommandState : queryCommandState, 14769 queryCommandValue : queryCommandValue, 14770 addCommands : addCommands 14771 }); 14772 14773 // Private methods 14774 14775 function execNativeCommand(command, ui, value) { 14776 if (ui === undef) 14777 ui = FALSE; 14778 14779 if (value === undef) 14780 value = null; 14781 14782 return editor.getDoc().execCommand(command, ui, value); 14783 }; 14784 14785 function isFormatMatch(name) { 14786 return formatter.match(name); 14787 }; 14788 14789 function toggleFormat(name, value) { 14790 formatter.toggle(name, value ? {value : value} : undef); 14791 }; 14792 14793 function storeSelection(type) { 14794 bookmark = selection.getBookmark(type); 14795 }; 14796 14797 function restoreSelection() { 14798 selection.moveToBookmark(bookmark); 14799 }; 14800 14801 // Add execCommand overrides 14802 addCommands({ 14803 // Ignore these, added for compatibility 14804 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, 14805 14806 // Add undo manager logic 14807 'mceEndUndoLevel,mceAddUndoLevel' : function() { 14808 editor.undoManager.add(); 14809 }, 14810 14811 'Cut,Copy,Paste' : function(command) { 14812 var doc = editor.getDoc(), failed; 14813 14814 // Try executing the native command 14815 try { 14816 execNativeCommand(command); 14817 } catch (ex) { 14818 // Command failed 14819 failed = TRUE; 14820 } 14821 14822 // Present alert message about clipboard access not being available 14823 if (failed || !doc.queryCommandSupported(command)) { 14824 if (tinymce.isGecko) { 14825 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { 14826 if (state) 14827 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); 14828 }); 14829 } else 14830 editor.windowManager.alert(editor.getLang('clipboard_no_support')); 14831 } 14832 }, 14833 14834 // Override unlink command 14835 unlink : function(command) { 14836 if (selection.isCollapsed()) 14837 selection.select(selection.getNode()); 14838 14839 execNativeCommand(command); 14840 selection.collapse(FALSE); 14841 }, 14842 14843 // Override justify commands to use the text formatter engine 14844 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 14845 var align = command.substring(7); 14846 14847 // Remove all other alignments first 14848 each('left,center,right,full'.split(','), function(name) { 14849 if (align != name) 14850 formatter.remove('align' + name); 14851 }); 14852 14853 toggleFormat('align' + align); 14854 execCommand('mceRepaint'); 14855 }, 14856 14857 // Override list commands to fix WebKit bug 14858 'InsertUnorderedList,InsertOrderedList' : function(command) { 14859 var listElm, listParent; 14860 14861 execNativeCommand(command); 14862 14863 // WebKit produces lists within block elements so we need to split them 14864 // we will replace the native list creation logic to custom logic later on 14865 // TODO: Remove this when the list creation logic is removed 14866 listElm = dom.getParent(selection.getNode(), 'ol,ul'); 14867 if (listElm) { 14868 listParent = listElm.parentNode; 14869 14870 // If list is within a text block then split that block 14871 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { 14872 storeSelection(); 14873 dom.split(listParent, listElm); 14874 restoreSelection(); 14875 } 14876 } 14877 }, 14878 14879 // Override commands to use the text formatter engine 14880 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 14881 toggleFormat(command); 14882 }, 14883 14884 // Override commands to use the text formatter engine 14885 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { 14886 toggleFormat(command, value); 14887 }, 14888 14889 FontSize : function(command, ui, value) { 14890 var fontClasses, fontSizes; 14891 14892 // Convert font size 1-7 to styles 14893 if (value >= 1 && value <= 7) { 14894 fontSizes = tinymce.explode(settings.font_size_style_values); 14895 fontClasses = tinymce.explode(settings.font_size_classes); 14896 14897 if (fontClasses) 14898 value = fontClasses[value - 1] || value; 14899 else 14900 value = fontSizes[value - 1] || value; 14901 } 14902 14903 toggleFormat(command, value); 14904 }, 14905 14906 RemoveFormat : function(command) { 14907 formatter.remove(command); 14908 }, 14909 14910 mceBlockQuote : function(command) { 14911 toggleFormat('blockquote'); 14912 }, 14913 14914 FormatBlock : function(command, ui, value) { 14915 return toggleFormat(value || 'p'); 14916 }, 14917 14918 mceCleanup : function() { 14919 var bookmark = selection.getBookmark(); 14920 14921 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); 14922 14923 selection.moveToBookmark(bookmark); 14924 }, 14925 14926 mceRemoveNode : function(command, ui, value) { 14927 var node = value || selection.getNode(); 14928 14929 // Make sure that the body node isn't removed 14930 if (node != editor.getBody()) { 14931 storeSelection(); 14932 editor.dom.remove(node, TRUE); 14933 restoreSelection(); 14934 } 14935 }, 14936 14937 mceSelectNodeDepth : function(command, ui, value) { 14938 var counter = 0; 14939 14940 dom.getParent(selection.getNode(), function(node) { 14941 if (node.nodeType == 1 && counter++ == value) { 14942 selection.select(node); 14943 return FALSE; 14944 } 14945 }, editor.getBody()); 14946 }, 14947 14948 mceSelectNode : function(command, ui, value) { 14949 selection.select(value); 14950 }, 14951 14952 mceInsertContent : function(command, ui, value) { 14953 var parser, serializer, parentNode, rootNode, fragment, args, 14954 marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement; 14955 14956 //selection.normalize(); 14957 14958 // Setup parser and serializer 14959 parser = editor.parser; 14960 serializer = new tinymce.html.Serializer({}, editor.schema); 14961 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>'; 14962 14963 // Run beforeSetContent handlers on the HTML to be inserted 14964 args = {content: value, format: 'html'}; 14965 selection.onBeforeSetContent.dispatch(selection, args); 14966 value = args.content; 14967 14968 // Add caret at end of contents if it's missing 14969 if (value.indexOf('{$caret}') == -1) 14970 value += '{$caret}'; 14971 14972 // Replace the caret marker with a span bookmark element 14973 value = value.replace(/\{\$caret\}/, bookmarkHtml); 14974 14975 // Insert node maker where we will insert the new HTML and get it's parent 14976 if (!selection.isCollapsed()) 14977 editor.getDoc().execCommand('Delete', false, null); 14978 14979 parentNode = selection.getNode(); 14980 14981 // Parse the fragment within the context of the parent node 14982 args = {context : parentNode.nodeName.toLowerCase()}; 14983 fragment = parser.parse(value, args); 14984 14985 // Move the caret to a more suitable location 14986 node = fragment.lastChild; 14987 if (node.attr('id') == 'mce_marker') { 14988 marker = node; 14989 14990 for (node = node.prev; node; node = node.walk(true)) { 14991 if (node.type == 3 || !dom.isBlock(node.name)) { 14992 node.parent.insert(marker, node, node.name === 'br'); 14993 break; 14994 } 14995 } 14996 } 14997 14998 // If parser says valid we can insert the contents into that parent 14999 if (!args.invalid) { 15000 value = serializer.serialize(fragment); 15001 15002 // Check if parent is empty or only has one BR element then set the innerHTML of that parent 15003 node = parentNode.firstChild; 15004 node2 = parentNode.lastChild; 15005 if (!node || (node === node2 && node.nodeName === 'BR')) 15006 dom.setHTML(parentNode, value); 15007 else 15008 selection.setContent(value); 15009 } else { 15010 // If the fragment was invalid within that context then we need 15011 // to parse and process the parent it's inserted into 15012 15013 // Insert bookmark node and get the parent 15014 selection.setContent(bookmarkHtml); 15015 parentNode = editor.selection.getNode(); 15016 rootNode = editor.getBody(); 15017 15018 // Opera will return the document node when selection is in root 15019 if (parentNode.nodeType == 9) 15020 parentNode = node = rootNode; 15021 else 15022 node = parentNode; 15023 15024 // Find the ancestor just before the root element 15025 while (node !== rootNode) { 15026 parentNode = node; 15027 node = node.parentNode; 15028 } 15029 15030 // Get the outer/inner HTML depending on if we are in the root and parser and serialize that 15031 value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); 15032 value = serializer.serialize( 15033 parser.parse( 15034 // Need to replace by using a function since $ in the contents would otherwise be a problem 15035 value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() { 15036 return serializer.serialize(fragment); 15037 }) 15038 ) 15039 ); 15040 15041 // Set the inner/outer HTML depending on if we are in the root or not 15042 if (parentNode == rootNode) 15043 dom.setHTML(rootNode, value); 15044 else 15045 dom.setOuterHTML(parentNode, value); 15046 } 15047 15048 marker = dom.get('mce_marker'); 15049 15050 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well 15051 nodeRect = dom.getRect(marker); 15052 viewPortRect = dom.getViewPort(editor.getWin()); 15053 15054 // Check if node is out side the viewport if it is then scroll to it 15055 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) || 15056 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) { 15057 viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody(); 15058 viewportBodyElement.scrollLeft = nodeRect.x; 15059 viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25; 15060 } 15061 15062 // Move selection before marker and remove it 15063 rng = dom.createRng(); 15064 15065 // If previous sibling is a text node set the selection to the end of that node 15066 node = marker.previousSibling; 15067 if (node && node.nodeType == 3) { 15068 rng.setStart(node, node.nodeValue.length); 15069 } else { 15070 // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node 15071 rng.setStartBefore(marker); 15072 rng.setEndBefore(marker); 15073 } 15074 15075 // Remove the marker node and set the new range 15076 dom.remove(marker); 15077 selection.setRng(rng); 15078 15079 // Dispatch after event and add any visual elements needed 15080 selection.onSetContent.dispatch(selection, args); 15081 editor.addVisual(); 15082 }, 15083 15084 mceInsertRawHTML : function(command, ui, value) { 15085 selection.setContent('tiny_mce_marker'); 15086 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value })); 15087 }, 15088 15089 mceToggleFormat : function(command, ui, value) { 15090 toggleFormat(value); 15091 }, 15092 15093 mceSetContent : function(command, ui, value) { 15094 editor.setContent(value); 15095 }, 15096 15097 'Indent,Outdent' : function(command) { 15098 var intentValue, indentUnit, value; 15099 15100 // Setup indent level 15101 intentValue = settings.indentation; 15102 indentUnit = /[a-z%]+$/i.exec(intentValue); 15103 intentValue = parseInt(intentValue); 15104 15105 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { 15106 // If forced_root_blocks is set to false we don't have a block to indent so lets create a div 15107 if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { 15108 formatter.apply('div'); 15109 } 15110 15111 each(selection.getSelectedBlocks(), function(element) { 15112 if (command == 'outdent') { 15113 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); 15114 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); 15115 } else 15116 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); 15117 }); 15118 } else 15119 execNativeCommand(command); 15120 }, 15121 15122 mceRepaint : function() { 15123 var bookmark; 15124 15125 if (tinymce.isGecko) { 15126 try { 15127 storeSelection(TRUE); 15128 15129 if (selection.getSel()) 15130 selection.getSel().selectAllChildren(editor.getBody()); 15131 15132 selection.collapse(TRUE); 15133 restoreSelection(); 15134 } catch (ex) { 15135 // Ignore 15136 } 15137 } 15138 }, 15139 15140 mceToggleFormat : function(command, ui, value) { 15141 formatter.toggle(value); 15142 }, 15143 15144 InsertHorizontalRule : function() { 15145 editor.execCommand('mceInsertContent', false, '<hr />'); 15146 }, 15147 15148 mceToggleVisualAid : function() { 15149 editor.hasVisual = !editor.hasVisual; 15150 editor.addVisual(); 15151 }, 15152 15153 mceReplaceContent : function(command, ui, value) { 15154 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); 15155 }, 15156 15157 mceInsertLink : function(command, ui, value) { 15158 var anchor; 15159 15160 if (typeof(value) == 'string') 15161 value = {href : value}; 15162 15163 anchor = dom.getParent(selection.getNode(), 'a'); 15164 15165 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. 15166 value.href = value.href.replace(' ', '%20'); 15167 15168 // Remove existing links if there could be child links or that the href isn't specified 15169 if (!anchor || !value.href) { 15170 formatter.remove('link'); 15171 } 15172 15173 // Apply new link to selection 15174 if (value.href) { 15175 formatter.apply('link', value, anchor); 15176 } 15177 }, 15178 15179 selectAll : function() { 15180 var root = dom.getRoot(), rng = dom.createRng(); 15181 15182 rng.setStart(root, 0); 15183 rng.setEnd(root, root.childNodes.length); 15184 15185 editor.selection.setRng(rng); 15186 } 15187 }); 15188 15189 // Add queryCommandState overrides 15190 addCommands({ 15191 // Override justify commands 15192 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 15193 var name = 'align' + command.substring(7); 15194 var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); 15195 var matches = tinymce.map(nodes, function(node) { 15196 return !!formatter.matchNode(node, name); 15197 }); 15198 return tinymce.inArray(matches, TRUE) !== -1; 15199 }, 15200 15201 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 15202 return isFormatMatch(command); 15203 }, 15204 15205 mceBlockQuote : function() { 15206 return isFormatMatch('blockquote'); 15207 }, 15208 15209 Outdent : function() { 15210 var node; 15211 15212 if (settings.inline_styles) { 15213 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15214 return TRUE; 15215 15216 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15217 return TRUE; 15218 } 15219 15220 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); 15221 }, 15222 15223 'InsertUnorderedList,InsertOrderedList' : function(command) { 15224 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL'); 15225 } 15226 }, 'state'); 15227 15228 // Add queryCommandValue overrides 15229 addCommands({ 15230 'FontSize,FontName' : function(command) { 15231 var value = 0, parent; 15232 15233 if (parent = dom.getParent(selection.getNode(), 'span')) { 15234 if (command == 'fontsize') 15235 value = parent.style.fontSize; 15236 else 15237 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); 15238 } 15239 15240 return value; 15241 } 15242 }, 'value'); 15243 15244 // Add undo manager logic 15245 addCommands({ 15246 Undo : function() { 15247 editor.undoManager.undo(); 15248 }, 15249 15250 Redo : function() { 15251 editor.undoManager.redo(); 15252 } 15253 }); 15254 }; 15255 })(tinymce); 15256 15257 (function(tinymce) { 15258 var Dispatcher = tinymce.util.Dispatcher; 15259 15260 tinymce.UndoManager = function(editor) { 15261 var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo; 15262 15263 function getContent() { 15264 // Remove whitespace before/after and remove pure bogus nodes 15265 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, '')); 15266 }; 15267 15268 function addNonTypingUndoLevel() { 15269 self.typing = false; 15270 self.add(); 15271 }; 15272 15273 // Create event instances 15274 onBeforeAdd = new Dispatcher(self); 15275 onAdd = new Dispatcher(self); 15276 onUndo = new Dispatcher(self); 15277 onRedo = new Dispatcher(self); 15278 15279 // Pass though onAdd event from UndoManager to Editor as onChange 15280 onAdd.add(function(undoman, level) { 15281 if (undoman.hasUndo()) 15282 return editor.onChange.dispatch(editor, level, undoman); 15283 }); 15284 15285 // Pass though onUndo event from UndoManager to Editor 15286 onUndo.add(function(undoman, level) { 15287 return editor.onUndo.dispatch(editor, level, undoman); 15288 }); 15289 15290 // Pass though onRedo event from UndoManager to Editor 15291 onRedo.add(function(undoman, level) { 15292 return editor.onRedo.dispatch(editor, level, undoman); 15293 }); 15294 15295 // Add initial undo level when the editor is initialized 15296 editor.onInit.add(function() { 15297 self.add(); 15298 }); 15299 15300 // Get position before an execCommand is processed 15301 editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) { 15302 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15303 self.beforeChange(); 15304 } 15305 }); 15306 15307 // Add undo level after an execCommand call was made 15308 editor.onExecCommand.add(function(ed, cmd, ui, val, args) { 15309 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15310 self.add(); 15311 } 15312 }); 15313 15314 // Add undo level on save contents, drag end and blur/focusout 15315 editor.onSaveContent.add(addNonTypingUndoLevel); 15316 editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel); 15317 editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) { 15318 if (!editor.removed && self.typing) { 15319 addNonTypingUndoLevel(); 15320 } 15321 }); 15322 15323 editor.onKeyUp.add(function(editor, e) { 15324 var keyCode = e.keyCode; 15325 15326 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) { 15327 addNonTypingUndoLevel(); 15328 } 15329 }); 15330 15331 editor.onKeyDown.add(function(editor, e) { 15332 var keyCode = e.keyCode; 15333 15334 // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter 15335 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) { 15336 if (self.typing) { 15337 addNonTypingUndoLevel(); 15338 } 15339 15340 return; 15341 } 15342 15343 // If key isn't shift,ctrl,alt,capslock,metakey 15344 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) { 15345 self.beforeChange(); 15346 self.typing = true; 15347 self.add(); 15348 } 15349 }); 15350 15351 editor.onMouseDown.add(function(editor, e) { 15352 if (self.typing) { 15353 addNonTypingUndoLevel(); 15354 } 15355 }); 15356 15357 // Add keyboard shortcuts for undo/redo keys 15358 editor.addShortcut('ctrl+z', 'undo_desc', 'Undo'); 15359 editor.addShortcut('ctrl+y', 'redo_desc', 'Redo'); 15360 15361 self = { 15362 // Explose for debugging reasons 15363 data : data, 15364 15365 typing : false, 15366 15367 onBeforeAdd: onBeforeAdd, 15368 15369 onAdd : onAdd, 15370 15371 onUndo : onUndo, 15372 15373 onRedo : onRedo, 15374 15375 beforeChange : function() { 15376 beforeBookmark = editor.selection.getBookmark(2, true); 15377 }, 15378 15379 add : function(level) { 15380 var i, settings = editor.settings, lastLevel; 15381 15382 level = level || {}; 15383 level.content = getContent(); 15384 15385 self.onBeforeAdd.dispatch(self, level); 15386 15387 // Add undo level if needed 15388 lastLevel = data[index]; 15389 if (lastLevel && lastLevel.content == level.content) 15390 return null; 15391 15392 // Set before bookmark on previous level 15393 if (data[index]) 15394 data[index].beforeBookmark = beforeBookmark; 15395 15396 // Time to compress 15397 if (settings.custom_undo_redo_levels) { 15398 if (data.length > settings.custom_undo_redo_levels) { 15399 for (i = 0; i < data.length - 1; i++) 15400 data[i] = data[i + 1]; 15401 15402 data.length--; 15403 index = data.length; 15404 } 15405 } 15406 15407 // Get a non intrusive normalized bookmark 15408 level.bookmark = editor.selection.getBookmark(2, true); 15409 15410 // Crop array if needed 15411 if (index < data.length - 1) 15412 data.length = index + 1; 15413 15414 data.push(level); 15415 index = data.length - 1; 15416 15417 self.onAdd.dispatch(self, level); 15418 editor.isNotDirty = 0; 15419 15420 return level; 15421 }, 15422 15423 undo : function() { 15424 var level, i; 15425 15426 if (self.typing) { 15427 self.add(); 15428 self.typing = false; 15429 } 15430 15431 if (index > 0) { 15432 level = data[--index]; 15433 15434 editor.setContent(level.content, {format : 'raw'}); 15435 editor.selection.moveToBookmark(level.beforeBookmark); 15436 15437 self.onUndo.dispatch(self, level); 15438 } 15439 15440 return level; 15441 }, 15442 15443 redo : function() { 15444 var level; 15445 15446 if (index < data.length - 1) { 15447 level = data[++index]; 15448 15449 editor.setContent(level.content, {format : 'raw'}); 15450 editor.selection.moveToBookmark(level.bookmark); 15451 15452 self.onRedo.dispatch(self, level); 15453 } 15454 15455 return level; 15456 }, 15457 15458 clear : function() { 15459 data = []; 15460 index = 0; 15461 self.typing = false; 15462 }, 15463 15464 hasUndo : function() { 15465 return index > 0 || this.typing; 15466 }, 15467 15468 hasRedo : function() { 15469 return index < data.length - 1 && !this.typing; 15470 } 15471 }; 15472 15473 return self; 15474 }; 15475 })(tinymce); 15476 15477 tinymce.ForceBlocks = function(editor) { 15478 var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements(); 15479 15480 function addRootBlocks() { 15481 var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument; 15482 15483 if (!node || node.nodeType !== 1 || !settings.forced_root_block) 15484 return; 15485 15486 // Check if node is wrapped in block 15487 while (node && node != rootNode) { 15488 if (blockElements[node.nodeName]) 15489 return; 15490 15491 node = node.parentNode; 15492 } 15493 15494 // Get current selection 15495 rng = selection.getRng(); 15496 if (rng.setStart) { 15497 startContainer = rng.startContainer; 15498 startOffset = rng.startOffset; 15499 endContainer = rng.endContainer; 15500 endOffset = rng.endOffset; 15501 } else { 15502 // Force control range into text range 15503 if (rng.item) { 15504 node = rng.item(0); 15505 rng = editor.getDoc().body.createTextRange(); 15506 rng.moveToElementText(node); 15507 } 15508 15509 isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc(); 15510 tmpRng = rng.duplicate(); 15511 tmpRng.collapse(true); 15512 startOffset = tmpRng.move('character', offset) * -1; 15513 15514 if (!tmpRng.collapsed) { 15515 tmpRng = rng.duplicate(); 15516 tmpRng.collapse(false); 15517 endOffset = (tmpRng.move('character', offset) * -1) - startOffset; 15518 } 15519 } 15520 15521 // Wrap non block elements and text nodes 15522 node = rootNode.firstChild; 15523 while (node) { 15524 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) { 15525 if (!rootBlockNode) { 15526 rootBlockNode = dom.create(settings.forced_root_block); 15527 node.parentNode.insertBefore(rootBlockNode, node); 15528 wrapped = true; 15529 } 15530 15531 tempNode = node; 15532 node = node.nextSibling; 15533 rootBlockNode.appendChild(tempNode); 15534 } else { 15535 rootBlockNode = null; 15536 node = node.nextSibling; 15537 } 15538 } 15539 15540 if (wrapped) { 15541 if (rng.setStart) { 15542 rng.setStart(startContainer, startOffset); 15543 rng.setEnd(endContainer, endOffset); 15544 selection.setRng(rng); 15545 } else { 15546 // Only select if the previous selection was inside the document to prevent auto focus in quirks mode 15547 if (isInEditorDocument) { 15548 try { 15549 rng = editor.getDoc().body.createTextRange(); 15550 rng.moveToElementText(rootNode); 15551 rng.collapse(true); 15552 rng.moveStart('character', startOffset); 15553 15554 if (endOffset > 0) 15555 rng.moveEnd('character', endOffset); 15556 15557 rng.select(); 15558 } catch (ex) { 15559 // Ignore 15560 } 15561 } 15562 } 15563 15564 editor.nodeChanged(); 15565 } 15566 }; 15567 15568 // Force root blocks 15569 if (settings.forced_root_block) { 15570 editor.onKeyUp.add(addRootBlocks); 15571 editor.onNodeChange.add(addRootBlocks); 15572 } 15573 }; 15574 15575 (function(tinymce) { 15576 // Shorten names 15577 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; 15578 15579 tinymce.create('tinymce.ControlManager', { 15580 ControlManager : function(ed, s) { 15581 var t = this, i; 15582 15583 s = s || {}; 15584 t.editor = ed; 15585 t.controls = {}; 15586 t.onAdd = new tinymce.util.Dispatcher(t); 15587 t.onPostRender = new tinymce.util.Dispatcher(t); 15588 t.prefix = s.prefix || ed.id + '_'; 15589 t._cls = {}; 15590 15591 t.onPostRender.add(function() { 15592 each(t.controls, function(c) { 15593 c.postRender(); 15594 }); 15595 }); 15596 }, 15597 15598 get : function(id) { 15599 return this.controls[this.prefix + id] || this.controls[id]; 15600 }, 15601 15602 setActive : function(id, s) { 15603 var c = null; 15604 15605 if (c = this.get(id)) 15606 c.setActive(s); 15607 15608 return c; 15609 }, 15610 15611 setDisabled : function(id, s) { 15612 var c = null; 15613 15614 if (c = this.get(id)) 15615 c.setDisabled(s); 15616 15617 return c; 15618 }, 15619 15620 add : function(c) { 15621 var t = this; 15622 15623 if (c) { 15624 t.controls[c.id] = c; 15625 t.onAdd.dispatch(c, t); 15626 } 15627 15628 return c; 15629 }, 15630 15631 createControl : function(name) { 15632 var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName; 15633 15634 // Build control factory cache 15635 if (!self.controlFactories) { 15636 self.controlFactories = []; 15637 each(editor.plugins, function(plugin) { 15638 if (plugin.createControl) { 15639 self.controlFactories.push(plugin); 15640 } 15641 }); 15642 } 15643 15644 // Create controls by asking cached factories 15645 factories = self.controlFactories; 15646 for (i = 0, l = factories.length; i < l; i++) { 15647 ctrl = factories[i].createControl(name, self); 15648 15649 if (ctrl) { 15650 return self.add(ctrl); 15651 } 15652 } 15653 15654 // Create sepearator 15655 if (name === "|" || name === "separator") { 15656 return self.createSeparator(); 15657 } 15658 15659 // Create control from button collection 15660 if (editor.buttons && (ctrl = editor.buttons[name])) { 15661 return self.createButton(name, ctrl); 15662 } 15663 15664 return self.add(ctrl); 15665 }, 15666 15667 createDropMenu : function(id, s, cc) { 15668 var t = this, ed = t.editor, c, bm, v, cls; 15669 15670 s = extend({ 15671 'class' : 'mceDropDown', 15672 constrain : ed.settings.constrain_menus 15673 }, s); 15674 15675 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; 15676 if (v = ed.getParam('skin_variant')) 15677 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); 15678 15679 s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : ''; 15680 15681 id = t.prefix + id; 15682 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; 15683 c = t.controls[id] = new cls(id, s); 15684 c.onAddItem.add(function(c, o) { 15685 var s = o.settings; 15686 15687 s.title = ed.getLang(s.title, s.title); 15688 15689 if (!s.onclick) { 15690 s.onclick = function(v) { 15691 if (s.cmd) 15692 ed.execCommand(s.cmd, s.ui || false, s.value); 15693 }; 15694 } 15695 }); 15696 15697 ed.onRemove.add(function() { 15698 c.destroy(); 15699 }); 15700 15701 // Fix for bug #1897785, #1898007 15702 if (tinymce.isIE) { 15703 c.onShowMenu.add(function() { 15704 // IE 8 needs focus in order to store away a range with the current collapsed caret location 15705 ed.focus(); 15706 15707 bm = ed.selection.getBookmark(1); 15708 }); 15709 15710 c.onHideMenu.add(function() { 15711 if (bm) { 15712 ed.selection.moveToBookmark(bm); 15713 bm = 0; 15714 } 15715 }); 15716 } 15717 15718 return t.add(c); 15719 }, 15720 15721 createListBox : function(id, s, cc) { 15722 var t = this, ed = t.editor, cmd, c, cls; 15723 15724 if (t.get(id)) 15725 return null; 15726 15727 s.title = ed.translate(s.title); 15728 s.scope = s.scope || ed; 15729 15730 if (!s.onselect) { 15731 s.onselect = function(v) { 15732 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15733 }; 15734 } 15735 15736 s = extend({ 15737 title : s.title, 15738 'class' : 'mce_' + id, 15739 scope : s.scope, 15740 control_manager : t 15741 }, s); 15742 15743 id = t.prefix + id; 15744 15745 15746 function useNativeListForAccessibility(ed) { 15747 return ed.settings.use_accessible_selects && !tinymce.isGecko 15748 } 15749 15750 if (ed.settings.use_native_selects || useNativeListForAccessibility(ed)) 15751 c = new tinymce.ui.NativeListBox(id, s); 15752 else { 15753 cls = cc || t._cls.listbox || tinymce.ui.ListBox; 15754 c = new cls(id, s, ed); 15755 } 15756 15757 t.controls[id] = c; 15758 15759 // Fix focus problem in Safari 15760 if (tinymce.isWebKit) { 15761 c.onPostRender.add(function(c, n) { 15762 // Store bookmark on mousedown 15763 Event.add(n, 'mousedown', function() { 15764 ed.bookmark = ed.selection.getBookmark(1); 15765 }); 15766 15767 // Restore on focus, since it might be lost 15768 Event.add(n, 'focus', function() { 15769 ed.selection.moveToBookmark(ed.bookmark); 15770 ed.bookmark = null; 15771 }); 15772 }); 15773 } 15774 15775 if (c.hideMenu) 15776 ed.onMouseDown.add(c.hideMenu, c); 15777 15778 return t.add(c); 15779 }, 15780 15781 createButton : function(id, s, cc) { 15782 var t = this, ed = t.editor, o, c, cls; 15783 15784 if (t.get(id)) 15785 return null; 15786 15787 s.title = ed.translate(s.title); 15788 s.label = ed.translate(s.label); 15789 s.scope = s.scope || ed; 15790 15791 if (!s.onclick && !s.menu_button) { 15792 s.onclick = function() { 15793 ed.execCommand(s.cmd, s.ui || false, s.value); 15794 }; 15795 } 15796 15797 s = extend({ 15798 title : s.title, 15799 'class' : 'mce_' + id, 15800 unavailable_prefix : ed.getLang('unavailable', ''), 15801 scope : s.scope, 15802 control_manager : t 15803 }, s); 15804 15805 id = t.prefix + id; 15806 15807 if (s.menu_button) { 15808 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; 15809 c = new cls(id, s, ed); 15810 ed.onMouseDown.add(c.hideMenu, c); 15811 } else { 15812 cls = t._cls.button || tinymce.ui.Button; 15813 c = new cls(id, s, ed); 15814 } 15815 15816 return t.add(c); 15817 }, 15818 15819 createMenuButton : function(id, s, cc) { 15820 s = s || {}; 15821 s.menu_button = 1; 15822 15823 return this.createButton(id, s, cc); 15824 }, 15825 15826 createSplitButton : function(id, s, cc) { 15827 var t = this, ed = t.editor, cmd, c, cls; 15828 15829 if (t.get(id)) 15830 return null; 15831 15832 s.title = ed.translate(s.title); 15833 s.scope = s.scope || ed; 15834 15835 if (!s.onclick) { 15836 s.onclick = function(v) { 15837 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15838 }; 15839 } 15840 15841 if (!s.onselect) { 15842 s.onselect = function(v) { 15843 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15844 }; 15845 } 15846 15847 s = extend({ 15848 title : s.title, 15849 'class' : 'mce_' + id, 15850 scope : s.scope, 15851 control_manager : t 15852 }, s); 15853 15854 id = t.prefix + id; 15855 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; 15856 c = t.add(new cls(id, s, ed)); 15857 ed.onMouseDown.add(c.hideMenu, c); 15858 15859 return c; 15860 }, 15861 15862 createColorSplitButton : function(id, s, cc) { 15863 var t = this, ed = t.editor, cmd, c, cls, bm; 15864 15865 if (t.get(id)) 15866 return null; 15867 15868 s.title = ed.translate(s.title); 15869 s.scope = s.scope || ed; 15870 15871 if (!s.onclick) { 15872 s.onclick = function(v) { 15873 if (tinymce.isIE) 15874 bm = ed.selection.getBookmark(1); 15875 15876 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15877 }; 15878 } 15879 15880 if (!s.onselect) { 15881 s.onselect = function(v) { 15882 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15883 }; 15884 } 15885 15886 s = extend({ 15887 title : s.title, 15888 'class' : 'mce_' + id, 15889 'menu_class' : ed.getParam('skin') + 'Skin', 15890 scope : s.scope, 15891 more_colors_title : ed.getLang('more_colors') 15892 }, s); 15893 15894 id = t.prefix + id; 15895 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; 15896 c = new cls(id, s, ed); 15897 ed.onMouseDown.add(c.hideMenu, c); 15898 15899 // Remove the menu element when the editor is removed 15900 ed.onRemove.add(function() { 15901 c.destroy(); 15902 }); 15903 15904 // Fix for bug #1897785, #1898007 15905 if (tinymce.isIE) { 15906 c.onShowMenu.add(function() { 15907 // IE 8 needs focus in order to store away a range with the current collapsed caret location 15908 ed.focus(); 15909 bm = ed.selection.getBookmark(1); 15910 }); 15911 15912 c.onHideMenu.add(function() { 15913 if (bm) { 15914 ed.selection.moveToBookmark(bm); 15915 bm = 0; 15916 } 15917 }); 15918 } 15919 15920 return t.add(c); 15921 }, 15922 15923 createToolbar : function(id, s, cc) { 15924 var c, t = this, cls; 15925 15926 id = t.prefix + id; 15927 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; 15928 c = new cls(id, s, t.editor); 15929 15930 if (t.get(id)) 15931 return null; 15932 15933 return t.add(c); 15934 }, 15935 15936 createToolbarGroup : function(id, s, cc) { 15937 var c, t = this, cls; 15938 id = t.prefix + id; 15939 cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup; 15940 c = new cls(id, s, t.editor); 15941 15942 if (t.get(id)) 15943 return null; 15944 15945 return t.add(c); 15946 }, 15947 15948 createSeparator : function(cc) { 15949 var cls = cc || this._cls.separator || tinymce.ui.Separator; 15950 15951 return new cls(); 15952 }, 15953 15954 setControlType : function(n, c) { 15955 return this._cls[n.toLowerCase()] = c; 15956 }, 15957 15958 destroy : function() { 15959 each(this.controls, function(c) { 15960 c.destroy(); 15961 }); 15962 15963 this.controls = null; 15964 } 15965 }); 15966 })(tinymce); 15967 15968 (function(tinymce) { 15969 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; 15970 15971 tinymce.create('tinymce.WindowManager', { 15972 WindowManager : function(ed) { 15973 var t = this; 15974 15975 t.editor = ed; 15976 t.onOpen = new Dispatcher(t); 15977 t.onClose = new Dispatcher(t); 15978 t.params = {}; 15979 t.features = {}; 15980 }, 15981 15982 open : function(s, p) { 15983 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; 15984 15985 // Default some options 15986 s = s || {}; 15987 p = p || {}; 15988 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window 15989 sh = isOpera ? vp.h : screen.height; 15990 s.name = s.name || 'mc_' + new Date().getTime(); 15991 s.width = parseInt(s.width || 320); 15992 s.height = parseInt(s.height || 240); 15993 s.resizable = true; 15994 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); 15995 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); 15996 p.inline = false; 15997 p.mce_width = s.width; 15998 p.mce_height = s.height; 15999 p.mce_auto_focus = s.auto_focus; 16000 16001 if (mo) { 16002 if (isIE) { 16003 s.center = true; 16004 s.help = false; 16005 s.dialogWidth = s.width + 'px'; 16006 s.dialogHeight = s.height + 'px'; 16007 s.scroll = s.scrollbars || false; 16008 } 16009 } 16010 16011 // Build features string 16012 each(s, function(v, k) { 16013 if (tinymce.is(v, 'boolean')) 16014 v = v ? 'yes' : 'no'; 16015 16016 if (!/^(name|url)$/.test(k)) { 16017 if (isIE && mo) 16018 f += (f ? ';' : '') + k + ':' + v; 16019 else 16020 f += (f ? ',' : '') + k + '=' + v; 16021 } 16022 }); 16023 16024 t.features = s; 16025 t.params = p; 16026 t.onOpen.dispatch(t, s, p); 16027 16028 u = s.url || s.file; 16029 u = tinymce._addVer(u); 16030 16031 try { 16032 if (isIE && mo) { 16033 w = 1; 16034 window.showModalDialog(u, window, f); 16035 } else 16036 w = window.open(u, s.name, f); 16037 } catch (ex) { 16038 // Ignore 16039 } 16040 16041 if (!w) 16042 alert(t.editor.getLang('popup_blocked')); 16043 }, 16044 16045 close : function(w) { 16046 w.close(); 16047 this.onClose.dispatch(this); 16048 }, 16049 16050 createInstance : function(cl, a, b, c, d, e) { 16051 var f = tinymce.resolve(cl); 16052 16053 return new f(a, b, c, d, e); 16054 }, 16055 16056 confirm : function(t, cb, s, w) { 16057 w = w || window; 16058 16059 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); 16060 }, 16061 16062 alert : function(tx, cb, s, w) { 16063 var t = this; 16064 16065 w = w || window; 16066 w.alert(t._decode(t.editor.getLang(tx, tx))); 16067 16068 if (cb) 16069 cb.call(s || t); 16070 }, 16071 16072 resizeBy : function(dw, dh, win) { 16073 win.resizeBy(dw, dh); 16074 }, 16075 16076 // Internal functions 16077 16078 _decode : function(s) { 16079 return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); 16080 } 16081 }); 16082 }(tinymce)); 16083 (function(tinymce) { 16084 tinymce.Formatter = function(ed) { 16085 var formats = {}, 16086 each = tinymce.each, 16087 dom = ed.dom, 16088 selection = ed.selection, 16089 TreeWalker = tinymce.dom.TreeWalker, 16090 rangeUtils = new tinymce.dom.RangeUtils(dom), 16091 isValid = ed.schema.isValidChild, 16092 isBlock = dom.isBlock, 16093 forcedRootBlock = ed.settings.forced_root_block, 16094 nodeIndex = dom.nodeIndex, 16095 INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF', 16096 MCE_ATTR_RE = /^(src|href|style)$/, 16097 FALSE = false, 16098 TRUE = true, 16099 formatChangeData, 16100 undef, 16101 getContentEditable = dom.getContentEditable; 16102 16103 function isArray(obj) { 16104 return obj instanceof Array; 16105 }; 16106 16107 function getParents(node, selector) { 16108 return dom.getParents(node, selector, dom.getRoot()); 16109 }; 16110 16111 function isCaretNode(node) { 16112 return node.nodeType === 1 && node.id === '_mce_caret'; 16113 }; 16114 16115 function defaultFormats() { 16116 register({ 16117 alignleft : [ 16118 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'}, 16119 {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}} 16120 ], 16121 16122 aligncenter : [ 16123 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'}, 16124 {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, 16125 {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}} 16126 ], 16127 16128 alignright : [ 16129 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'}, 16130 {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}} 16131 ], 16132 16133 alignfull : [ 16134 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'} 16135 ], 16136 16137 bold : [ 16138 {inline : 'strong', remove : 'all'}, 16139 {inline : 'span', styles : {fontWeight : 'bold'}}, 16140 {inline : 'b', remove : 'all'} 16141 ], 16142 16143 italic : [ 16144 {inline : 'em', remove : 'all'}, 16145 {inline : 'span', styles : {fontStyle : 'italic'}}, 16146 {inline : 'i', remove : 'all'} 16147 ], 16148 16149 underline : [ 16150 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, 16151 {inline : 'u', remove : 'all'} 16152 ], 16153 16154 strikethrough : [ 16155 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, 16156 {inline : 'strike', remove : 'all'} 16157 ], 16158 16159 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false}, 16160 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false}, 16161 fontname : {inline : 'span', styles : {fontFamily : '%value'}}, 16162 fontsize : {inline : 'span', styles : {fontSize : '%value'}}, 16163 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, 16164 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, 16165 subscript : {inline : 'sub'}, 16166 superscript : {inline : 'sup'}, 16167 16168 link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true, 16169 onmatch : function(node) { 16170 return true; 16171 }, 16172 16173 onformat : function(elm, fmt, vars) { 16174 each(vars, function(value, key) { 16175 dom.setAttrib(elm, key, value); 16176 }); 16177 } 16178 }, 16179 16180 removeformat : [ 16181 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, 16182 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, 16183 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} 16184 ] 16185 }); 16186 16187 // Register default block formats 16188 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { 16189 register(name, {block : name, remove : 'all'}); 16190 }); 16191 16192 // Register user defined formats 16193 register(ed.settings.formats); 16194 }; 16195 16196 function addKeyboardShortcuts() { 16197 // Add some inline shortcuts 16198 ed.addShortcut('ctrl+b', 'bold_desc', 'Bold'); 16199 ed.addShortcut('ctrl+i', 'italic_desc', 'Italic'); 16200 ed.addShortcut('ctrl+u', 'underline_desc', 'Underline'); 16201 16202 // BlockFormat shortcuts keys 16203 for (var i = 1; i <= 6; i++) { 16204 ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); 16205 } 16206 16207 ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']); 16208 ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']); 16209 ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']); 16210 }; 16211 16212 // Public functions 16213 16214 function get(name) { 16215 return name ? formats[name] : formats; 16216 }; 16217 16218 function register(name, format) { 16219 if (name) { 16220 if (typeof(name) !== 'string') { 16221 each(name, function(format, name) { 16222 register(name, format); 16223 }); 16224 } else { 16225 // Force format into array and add it to internal collection 16226 format = format.length ? format : [format]; 16227 16228 each(format, function(format) { 16229 // Set deep to false by default on selector formats this to avoid removing 16230 // alignment on images inside paragraphs when alignment is changed on paragraphs 16231 if (format.deep === undef) 16232 format.deep = !format.selector; 16233 16234 // Default to true 16235 if (format.split === undef) 16236 format.split = !format.selector || format.inline; 16237 16238 // Default to true 16239 if (format.remove === undef && format.selector && !format.inline) 16240 format.remove = 'none'; 16241 16242 // Mark format as a mixed format inline + block level 16243 if (format.selector && format.inline) { 16244 format.mixed = true; 16245 format.block_expand = true; 16246 } 16247 16248 // Split classes if needed 16249 if (typeof(format.classes) === 'string') 16250 format.classes = format.classes.split(/\s+/); 16251 }); 16252 16253 formats[name] = format; 16254 } 16255 } 16256 }; 16257 16258 var getTextDecoration = function(node) { 16259 var decoration; 16260 16261 ed.dom.getParent(node, function(n) { 16262 decoration = ed.dom.getStyle(n, 'text-decoration'); 16263 return decoration && decoration !== 'none'; 16264 }); 16265 16266 return decoration; 16267 }; 16268 16269 var processUnderlineAndColor = function(node) { 16270 var textDecoration; 16271 if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { 16272 textDecoration = getTextDecoration(node.parentNode); 16273 if (ed.dom.getStyle(node, 'color') && textDecoration) { 16274 ed.dom.setStyle(node, 'text-decoration', textDecoration); 16275 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { 16276 ed.dom.setStyle(node, 'text-decoration', null); 16277 } 16278 } 16279 }; 16280 16281 function apply(name, vars, node) { 16282 var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed(); 16283 16284 function setElementFormat(elm, fmt) { 16285 fmt = fmt || format; 16286 16287 if (elm) { 16288 if (fmt.onformat) { 16289 fmt.onformat(elm, fmt, vars, node); 16290 } 16291 16292 each(fmt.styles, function(value, name) { 16293 dom.setStyle(elm, name, replaceVars(value, vars)); 16294 }); 16295 16296 each(fmt.attributes, function(value, name) { 16297 dom.setAttrib(elm, name, replaceVars(value, vars)); 16298 }); 16299 16300 each(fmt.classes, function(value) { 16301 value = replaceVars(value, vars); 16302 16303 if (!dom.hasClass(elm, value)) 16304 dom.addClass(elm, value); 16305 }); 16306 } 16307 }; 16308 function adjustSelectionToVisibleSelection() { 16309 function findSelectionEnd(start, end) { 16310 var walker = new TreeWalker(end); 16311 for (node = walker.current(); node; node = walker.prev()) { 16312 if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') { 16313 return node; 16314 } 16315 } 16316 }; 16317 16318 // Adjust selection so that a end container with a end offset of zero is not included in the selection 16319 // as this isn't visible to the user. 16320 var rng = ed.selection.getRng(); 16321 var start = rng.startContainer; 16322 var end = rng.endContainer; 16323 16324 if (start != end && rng.endOffset === 0) { 16325 var newEnd = findSelectionEnd(start, end); 16326 var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; 16327 16328 rng.setEnd(newEnd, endOffset); 16329 } 16330 16331 return rng; 16332 } 16333 16334 function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){ 16335 var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm; 16336 16337 // find the index of the first child list. 16338 each(node.childNodes, function(n, index) { 16339 if (n.nodeName === "UL" || n.nodeName === "OL") { 16340 listIndex = index; 16341 list = n; 16342 return false; 16343 } 16344 }); 16345 16346 // get the index of the bookmarks 16347 each(node.childNodes, function(n, index) { 16348 if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") { 16349 if (n.id == bookmark.id + "_start") { 16350 startIndex = index; 16351 } else if (n.id == bookmark.id + "_end") { 16352 endIndex = index; 16353 } 16354 } 16355 }); 16356 16357 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally 16358 if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) { 16359 each(tinymce.grep(node.childNodes), process); 16360 return 0; 16361 } else { 16362 currentWrapElm = dom.clone(wrapElm, FALSE); 16363 16364 // create a list of the nodes on the same side of the list as the selection 16365 each(tinymce.grep(node.childNodes), function(n, index) { 16366 if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) { 16367 nodes.push(n); 16368 n.parentNode.removeChild(n); 16369 } 16370 }); 16371 16372 // insert the wrapping element either before or after the list. 16373 if (startIndex < listIndex) { 16374 node.insertBefore(currentWrapElm, list); 16375 } else if (startIndex > listIndex) { 16376 node.insertBefore(currentWrapElm, list.nextSibling); 16377 } 16378 16379 // add the new nodes to the list. 16380 newWrappers.push(currentWrapElm); 16381 16382 each(nodes, function(node) { 16383 currentWrapElm.appendChild(node); 16384 }); 16385 16386 return currentWrapElm; 16387 } 16388 }; 16389 16390 function applyRngStyle(rng, bookmark, node_specific) { 16391 var newWrappers = [], wrapName, wrapElm, contentEditable = true; 16392 16393 // Setup wrapper element 16394 wrapName = format.inline || format.block; 16395 wrapElm = dom.create(wrapName); 16396 setElementFormat(wrapElm); 16397 16398 rangeUtils.walk(rng, function(nodes) { 16399 var currentWrapElm; 16400 16401 function process(node) { 16402 var nodeName, parentName, found, hasContentEditableState, lastContentEditable; 16403 16404 lastContentEditable = contentEditable; 16405 nodeName = node.nodeName.toLowerCase(); 16406 parentName = node.parentNode.nodeName.toLowerCase(); 16407 16408 // Node has a contentEditable value 16409 if (node.nodeType === 1 && getContentEditable(node)) { 16410 lastContentEditable = contentEditable; 16411 contentEditable = getContentEditable(node) === "true"; 16412 hasContentEditableState = true; // We don't want to wrap the container only it's children 16413 } 16414 16415 // Stop wrapping on br elements 16416 if (isEq(nodeName, 'br')) { 16417 currentWrapElm = 0; 16418 16419 // Remove any br elements when we wrap things 16420 if (format.block) 16421 dom.remove(node); 16422 16423 return; 16424 } 16425 16426 // If node is wrapper type 16427 if (format.wrapper && matchNode(node, name, vars)) { 16428 currentWrapElm = 0; 16429 return; 16430 } 16431 16432 // Can we rename the block 16433 if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) { 16434 node = dom.rename(node, wrapName); 16435 setElementFormat(node); 16436 newWrappers.push(node); 16437 currentWrapElm = 0; 16438 return; 16439 } 16440 16441 // Handle selector patterns 16442 if (format.selector) { 16443 // Look for matching formats 16444 each(formatList, function(format) { 16445 // Check collapsed state if it exists 16446 if ('collapsed' in format && format.collapsed !== isCollapsed) { 16447 return; 16448 } 16449 16450 if (dom.is(node, format.selector) && !isCaretNode(node)) { 16451 setElementFormat(node, format); 16452 found = true; 16453 } 16454 }); 16455 16456 // Continue processing if a selector match wasn't found and a inline element is defined 16457 if (!format.inline || found) { 16458 currentWrapElm = 0; 16459 return; 16460 } 16461 } 16462 16463 // Is it valid to wrap this item 16464 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) && 16465 !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) { 16466 // Start wrapping 16467 if (!currentWrapElm) { 16468 // Wrap the node 16469 currentWrapElm = dom.clone(wrapElm, FALSE); 16470 node.parentNode.insertBefore(currentWrapElm, node); 16471 newWrappers.push(currentWrapElm); 16472 } 16473 16474 currentWrapElm.appendChild(node); 16475 } else if (nodeName == 'li' && bookmark) { 16476 // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element. 16477 currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process); 16478 } else { 16479 // Start a new wrapper for possible children 16480 currentWrapElm = 0; 16481 16482 each(tinymce.grep(node.childNodes), process); 16483 16484 if (hasContentEditableState) { 16485 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 16486 } 16487 16488 // End the last wrapper 16489 currentWrapElm = 0; 16490 } 16491 }; 16492 16493 // Process siblings from range 16494 each(nodes, process); 16495 }); 16496 16497 // Wrap links inside as well, for example color inside a link when the wrapper is around the link 16498 if (format.wrap_links === false) { 16499 each(newWrappers, function(node) { 16500 function process(node) { 16501 var i, currentWrapElm, children; 16502 16503 if (node.nodeName === 'A') { 16504 currentWrapElm = dom.clone(wrapElm, FALSE); 16505 newWrappers.push(currentWrapElm); 16506 16507 children = tinymce.grep(node.childNodes); 16508 for (i = 0; i < children.length; i++) 16509 currentWrapElm.appendChild(children[i]); 16510 16511 node.appendChild(currentWrapElm); 16512 } 16513 16514 each(tinymce.grep(node.childNodes), process); 16515 }; 16516 16517 process(node); 16518 }); 16519 } 16520 16521 // Cleanup 16522 16523 each(newWrappers, function(node) { 16524 var childCount; 16525 16526 function getChildCount(node) { 16527 var count = 0; 16528 16529 each(node.childNodes, function(node) { 16530 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) 16531 count++; 16532 }); 16533 16534 return count; 16535 }; 16536 16537 function mergeStyles(node) { 16538 var child, clone; 16539 16540 each(node.childNodes, function(node) { 16541 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { 16542 child = node; 16543 return FALSE; // break loop 16544 } 16545 }); 16546 16547 // If child was found and of the same type as the current node 16548 if (child && matchName(child, format)) { 16549 clone = dom.clone(child, FALSE); 16550 setElementFormat(clone); 16551 16552 dom.replace(clone, node, TRUE); 16553 dom.remove(child, 1); 16554 } 16555 16556 return clone || node; 16557 }; 16558 16559 childCount = getChildCount(node); 16560 16561 // Remove empty nodes but only if there is multiple wrappers and they are not block 16562 // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at 16563 if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { 16564 dom.remove(node, 1); 16565 return; 16566 } 16567 16568 if (format.inline || format.wrapper) { 16569 // Merges the current node with it's children of similar type to reduce the number of elements 16570 if (!format.exact && childCount === 1) 16571 node = mergeStyles(node); 16572 16573 // Remove/merge children 16574 each(formatList, function(format) { 16575 // Merge all children of similar type will move styles from child to parent 16576 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span> 16577 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span> 16578 each(dom.select(format.inline, node), function(child) { 16579 var parent; 16580 16581 // When wrap_links is set to false we don't want 16582 // to remove the format on children within links 16583 if (format.wrap_links === false) { 16584 parent = child.parentNode; 16585 16586 do { 16587 if (parent.nodeName === 'A') 16588 return; 16589 } while (parent = parent.parentNode); 16590 } 16591 16592 removeFormat(format, vars, child, format.exact ? child : null); 16593 }); 16594 }); 16595 16596 // Remove child if direct parent is of same type 16597 if (matchNode(node.parentNode, name, vars)) { 16598 dom.remove(node, 1); 16599 node = 0; 16600 return TRUE; 16601 } 16602 16603 // Look for parent with similar style format 16604 if (format.merge_with_parents) { 16605 dom.getParent(node.parentNode, function(parent) { 16606 if (matchNode(parent, name, vars)) { 16607 dom.remove(node, 1); 16608 node = 0; 16609 return TRUE; 16610 } 16611 }); 16612 } 16613 16614 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b> 16615 if (node && format.merge_siblings !== false) { 16616 node = mergeSiblings(getNonWhiteSpaceSibling(node), node); 16617 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); 16618 } 16619 } 16620 }); 16621 }; 16622 16623 if (format) { 16624 if (node) { 16625 if (node.nodeType) { 16626 rng = dom.createRng(); 16627 rng.setStartBefore(node); 16628 rng.setEndAfter(node); 16629 applyRngStyle(expandRng(rng, formatList), null, true); 16630 } else { 16631 applyRngStyle(node, null, true); 16632 } 16633 } else { 16634 if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 16635 // Obtain selection node before selection is unselected by applyRngStyle() 16636 var curSelNode = ed.selection.getNode(); 16637 16638 // If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false 16639 // It's kind of a hack but people should be using the default block type P since all desktop editors work that way 16640 if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) { 16641 apply(formatList[0].defaultBlock); 16642 } 16643 16644 // Apply formatting to selection 16645 ed.selection.setRng(adjustSelectionToVisibleSelection()); 16646 bookmark = selection.getBookmark(); 16647 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); 16648 16649 // Colored nodes should be underlined so that the color of the underline matches the text color. 16650 if (format.styles && (format.styles.color || format.styles.textDecoration)) { 16651 tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes'); 16652 processUnderlineAndColor(curSelNode); 16653 } 16654 16655 selection.moveToBookmark(bookmark); 16656 moveStart(selection.getRng(TRUE)); 16657 ed.nodeChanged(); 16658 } else 16659 performCaretAction('apply', name, vars); 16660 } 16661 } 16662 }; 16663 16664 function remove(name, vars, node) { 16665 var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true; 16666 16667 // Merges the styles for each node 16668 function process(node) { 16669 var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState; 16670 16671 // Node has a contentEditable value 16672 if (node.nodeType === 1 && getContentEditable(node)) { 16673 lastContentEditable = contentEditable; 16674 contentEditable = getContentEditable(node) === "true"; 16675 hasContentEditableState = true; // We don't want to wrap the container only it's children 16676 } 16677 16678 // Grab the children first since the nodelist might be changed 16679 children = tinymce.grep(node.childNodes); 16680 16681 // Process current node 16682 if (contentEditable && !hasContentEditableState) { 16683 for (i = 0, l = formatList.length; i < l; i++) { 16684 if (removeFormat(formatList[i], vars, node, node)) 16685 break; 16686 } 16687 } 16688 16689 // Process the children 16690 if (format.deep) { 16691 if (children.length) { 16692 for (i = 0, l = children.length; i < l; i++) 16693 process(children[i]); 16694 16695 if (hasContentEditableState) { 16696 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 16697 } 16698 } 16699 } 16700 }; 16701 16702 function findFormatRoot(container) { 16703 var formatRoot; 16704 16705 // Find format root 16706 each(getParents(container.parentNode).reverse(), function(parent) { 16707 var format; 16708 16709 // Find format root element 16710 if (!formatRoot && parent.id != '_start' && parent.id != '_end') { 16711 // Is the node matching the format we are looking for 16712 format = matchNode(parent, name, vars); 16713 if (format && format.split !== false) 16714 formatRoot = parent; 16715 } 16716 }); 16717 16718 return formatRoot; 16719 }; 16720 16721 function wrapAndSplit(format_root, container, target, split) { 16722 var parent, clone, lastClone, firstClone, i, formatRootParent; 16723 16724 // Format root found then clone formats and split it 16725 if (format_root) { 16726 formatRootParent = format_root.parentNode; 16727 16728 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { 16729 clone = dom.clone(parent, FALSE); 16730 16731 for (i = 0; i < formatList.length; i++) { 16732 if (removeFormat(formatList[i], vars, clone, clone)) { 16733 clone = 0; 16734 break; 16735 } 16736 } 16737 16738 // Build wrapper node 16739 if (clone) { 16740 if (lastClone) 16741 clone.appendChild(lastClone); 16742 16743 if (!firstClone) 16744 firstClone = clone; 16745 16746 lastClone = clone; 16747 } 16748 } 16749 16750 // Never split block elements if the format is mixed 16751 if (split && (!format.mixed || !isBlock(format_root))) 16752 container = dom.split(format_root, container); 16753 16754 // Wrap container in cloned formats 16755 if (lastClone) { 16756 target.parentNode.insertBefore(lastClone, target); 16757 firstClone.appendChild(target); 16758 } 16759 } 16760 16761 return container; 16762 }; 16763 16764 function splitToFormatRoot(container) { 16765 return wrapAndSplit(findFormatRoot(container), container, container, true); 16766 }; 16767 16768 function unwrap(start) { 16769 var node = dom.get(start ? '_start' : '_end'), 16770 out = node[start ? 'firstChild' : 'lastChild']; 16771 16772 // If the end is placed within the start the result will be removed 16773 // So this checks if the out node is a bookmark node if it is it 16774 // checks for another more suitable node 16775 if (isBookmarkNode(out)) 16776 out = out[start ? 'firstChild' : 'lastChild']; 16777 16778 dom.remove(node, true); 16779 16780 return out; 16781 }; 16782 16783 function removeRngStyle(rng) { 16784 var startContainer, endContainer, node; 16785 16786 rng = expandRng(rng, formatList, TRUE); 16787 16788 if (format.split) { 16789 startContainer = getContainer(rng, TRUE); 16790 endContainer = getContainer(rng); 16791 16792 if (startContainer != endContainer) { 16793 // WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead 16794 // This will happen if you tripple click a table cell and use remove formatting 16795 if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) { 16796 startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer; 16797 } 16798 16799 // Wrap start/end nodes in span element since these might be cloned/moved 16800 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'}); 16801 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'}); 16802 16803 // Split start/end 16804 splitToFormatRoot(startContainer); 16805 splitToFormatRoot(endContainer); 16806 16807 // Unwrap start/end to get real elements again 16808 startContainer = unwrap(TRUE); 16809 endContainer = unwrap(); 16810 } else 16811 startContainer = endContainer = splitToFormatRoot(startContainer); 16812 16813 // Update range positions since they might have changed after the split operations 16814 rng.startContainer = startContainer.parentNode; 16815 rng.startOffset = nodeIndex(startContainer); 16816 rng.endContainer = endContainer.parentNode; 16817 rng.endOffset = nodeIndex(endContainer) + 1; 16818 } 16819 16820 // Remove items between start/end 16821 rangeUtils.walk(rng, function(nodes) { 16822 each(nodes, function(node) { 16823 process(node); 16824 16825 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. 16826 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') { 16827 removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node); 16828 } 16829 }); 16830 }); 16831 }; 16832 16833 // Handle node 16834 if (node) { 16835 if (node.nodeType) { 16836 rng = dom.createRng(); 16837 rng.setStartBefore(node); 16838 rng.setEndAfter(node); 16839 removeRngStyle(rng); 16840 } else { 16841 removeRngStyle(node); 16842 } 16843 16844 return; 16845 } 16846 16847 if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 16848 bookmark = selection.getBookmark(); 16849 removeRngStyle(selection.getRng(TRUE)); 16850 selection.moveToBookmark(bookmark); 16851 16852 // Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node 16853 if (format.inline && match(name, vars, selection.getStart())) { 16854 moveStart(selection.getRng(true)); 16855 } 16856 16857 ed.nodeChanged(); 16858 } else 16859 performCaretAction('remove', name, vars); 16860 }; 16861 16862 function toggle(name, vars, node) { 16863 var fmt = get(name); 16864 16865 if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) 16866 remove(name, vars, node); 16867 else 16868 apply(name, vars, node); 16869 }; 16870 16871 function matchNode(node, name, vars, similar) { 16872 var formatList = get(name), format, i, classes; 16873 16874 function matchItems(node, format, item_name) { 16875 var key, value, items = format[item_name], i; 16876 16877 // Custom match 16878 if (format.onmatch) { 16879 return format.onmatch(node, format, item_name); 16880 } 16881 16882 // Check all items 16883 if (items) { 16884 // Non indexed object 16885 if (items.length === undef) { 16886 for (key in items) { 16887 if (items.hasOwnProperty(key)) { 16888 if (item_name === 'attributes') 16889 value = dom.getAttrib(node, key); 16890 else 16891 value = getStyle(node, key); 16892 16893 if (similar && !value && !format.exact) 16894 return; 16895 16896 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars))) 16897 return; 16898 } 16899 } 16900 } else { 16901 // Only one match needed for indexed arrays 16902 for (i = 0; i < items.length; i++) { 16903 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) 16904 return format; 16905 } 16906 } 16907 } 16908 16909 return format; 16910 }; 16911 16912 if (formatList && node) { 16913 // Check each format in list 16914 for (i = 0; i < formatList.length; i++) { 16915 format = formatList[i]; 16916 16917 // Name name, attributes, styles and classes 16918 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { 16919 // Match classes 16920 if (classes = format.classes) { 16921 for (i = 0; i < classes.length; i++) { 16922 if (!dom.hasClass(node, classes[i])) 16923 return; 16924 } 16925 } 16926 16927 return format; 16928 } 16929 } 16930 } 16931 }; 16932 16933 function match(name, vars, node) { 16934 var startNode; 16935 16936 function matchParents(node) { 16937 // Find first node with similar format settings 16938 node = dom.getParent(node, function(node) { 16939 return !!matchNode(node, name, vars, true); 16940 }); 16941 16942 // Do an exact check on the similar format element 16943 return matchNode(node, name, vars); 16944 }; 16945 16946 // Check specified node 16947 if (node) 16948 return matchParents(node); 16949 16950 // Check selected node 16951 node = selection.getNode(); 16952 if (matchParents(node)) 16953 return TRUE; 16954 16955 // Check start node if it's different 16956 startNode = selection.getStart(); 16957 if (startNode != node) { 16958 if (matchParents(startNode)) 16959 return TRUE; 16960 } 16961 16962 return FALSE; 16963 }; 16964 16965 function matchAll(names, vars) { 16966 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; 16967 16968 // Check start of selection for formats 16969 startElement = selection.getStart(); 16970 dom.getParent(startElement, function(node) { 16971 var i, name; 16972 16973 for (i = 0; i < names.length; i++) { 16974 name = names[i]; 16975 16976 if (!checkedMap[name] && matchNode(node, name, vars)) { 16977 checkedMap[name] = true; 16978 matchedFormatNames.push(name); 16979 } 16980 } 16981 }, dom.getRoot()); 16982 16983 return matchedFormatNames; 16984 }; 16985 16986 function canApply(name) { 16987 var formatList = get(name), startNode, parents, i, x, selector; 16988 16989 if (formatList) { 16990 startNode = selection.getStart(); 16991 parents = getParents(startNode); 16992 16993 for (x = formatList.length - 1; x >= 0; x--) { 16994 selector = formatList[x].selector; 16995 16996 // Format is not selector based, then always return TRUE 16997 if (!selector) 16998 return TRUE; 16999 17000 for (i = parents.length - 1; i >= 0; i--) { 17001 if (dom.is(parents[i], selector)) 17002 return TRUE; 17003 } 17004 } 17005 } 17006 17007 return FALSE; 17008 }; 17009 17010 function formatChanged(formats, callback) { 17011 var currentFormats; 17012 17013 // Setup format node change logic 17014 if (!formatChangeData) { 17015 formatChangeData = {}; 17016 currentFormats = {}; 17017 17018 ed.onNodeChange.addToTop(function(ed, cm, node) { 17019 var parents = getParents(node), matchedFormats = {}; 17020 17021 // Check for new formats 17022 each(formatChangeData, function(callbacks, format) { 17023 each(parents, function(node) { 17024 if (matchNode(node, format, {}, true)) { 17025 if (!currentFormats[format]) { 17026 // Execute callbacks 17027 each(callbacks, function(callback) { 17028 callback(true, {node: node, format: format, parents: parents}); 17029 }); 17030 17031 currentFormats[format] = callbacks; 17032 } 17033 17034 matchedFormats[format] = callbacks; 17035 return false; 17036 } 17037 }); 17038 }); 17039 17040 // Check if current formats still match 17041 each(currentFormats, function(callbacks, format) { 17042 if (!matchedFormats[format]) { 17043 delete currentFormats[format]; 17044 17045 each(callbacks, function(callback) { 17046 callback(false, {node: node, format: format, parents: parents}); 17047 }); 17048 } 17049 }); 17050 }); 17051 } 17052 17053 // Add format listeners 17054 each(formats.split(','), function(format) { 17055 if (!formatChangeData[format]) { 17056 formatChangeData[format] = []; 17057 } 17058 17059 formatChangeData[format].push(callback); 17060 }); 17061 17062 return this; 17063 }; 17064 17065 // Expose to public 17066 tinymce.extend(this, { 17067 get : get, 17068 register : register, 17069 apply : apply, 17070 remove : remove, 17071 toggle : toggle, 17072 match : match, 17073 matchAll : matchAll, 17074 matchNode : matchNode, 17075 canApply : canApply, 17076 formatChanged: formatChanged 17077 }); 17078 17079 // Initialize 17080 defaultFormats(); 17081 addKeyboardShortcuts(); 17082 17083 // Private functions 17084 17085 function matchName(node, format) { 17086 // Check for inline match 17087 if (isEq(node, format.inline)) 17088 return TRUE; 17089 17090 // Check for block match 17091 if (isEq(node, format.block)) 17092 return TRUE; 17093 17094 // Check for selector match 17095 if (format.selector) 17096 return dom.is(node, format.selector); 17097 }; 17098 17099 function isEq(str1, str2) { 17100 str1 = str1 || ''; 17101 str2 = str2 || ''; 17102 17103 str1 = '' + (str1.nodeName || str1); 17104 str2 = '' + (str2.nodeName || str2); 17105 17106 return str1.toLowerCase() == str2.toLowerCase(); 17107 }; 17108 17109 function getStyle(node, name) { 17110 var styleVal = dom.getStyle(node, name); 17111 17112 // Force the format to hex 17113 if (name == 'color' || name == 'backgroundColor') 17114 styleVal = dom.toHex(styleVal); 17115 17116 // Opera will return bold as 700 17117 if (name == 'fontWeight' && styleVal == 700) 17118 styleVal = 'bold'; 17119 17120 return '' + styleVal; 17121 }; 17122 17123 function replaceVars(value, vars) { 17124 if (typeof(value) != "string") 17125 value = value(vars); 17126 else if (vars) { 17127 value = value.replace(/%(\w+)/g, function(str, name) { 17128 return vars[name] || str; 17129 }); 17130 } 17131 17132 return value; 17133 }; 17134 17135 function isWhiteSpaceNode(node) { 17136 return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); 17137 }; 17138 17139 function wrap(node, name, attrs) { 17140 var wrapper = dom.create(name, attrs); 17141 17142 node.parentNode.insertBefore(wrapper, node); 17143 wrapper.appendChild(node); 17144 17145 return wrapper; 17146 }; 17147 17148 function expandRng(rng, format, remove) { 17149 var sibling, lastIdx, leaf, endPoint, 17150 startContainer = rng.startContainer, 17151 startOffset = rng.startOffset, 17152 endContainer = rng.endContainer, 17153 endOffset = rng.endOffset; 17154 17155 // This function walks up the tree if there is no siblings before/after the node 17156 function findParentContainer(start) { 17157 var container, parent, child, sibling, siblingName, root; 17158 17159 container = parent = start ? startContainer : endContainer; 17160 siblingName = start ? 'previousSibling' : 'nextSibling'; 17161 root = dom.getRoot(); 17162 17163 // If it's a text node and the offset is inside the text 17164 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { 17165 if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { 17166 return container; 17167 } 17168 } 17169 17170 for (;;) { 17171 // Stop expanding on block elements 17172 if (!format[0].block_expand && isBlock(parent)) 17173 return parent; 17174 17175 // Walk left/right 17176 for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { 17177 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) { 17178 return parent; 17179 } 17180 } 17181 17182 // Check if we can move up are we at root level or body level 17183 if (parent.parentNode == root) { 17184 container = parent; 17185 break; 17186 } 17187 17188 parent = parent.parentNode; 17189 } 17190 17191 return container; 17192 }; 17193 17194 // This function walks down the tree to find the leaf at the selection. 17195 // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. 17196 function findLeaf(node, offset) { 17197 if (offset === undef) 17198 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17199 while (node && node.hasChildNodes()) { 17200 node = node.childNodes[offset]; 17201 if (node) 17202 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17203 } 17204 return { node: node, offset: offset }; 17205 } 17206 17207 // If index based start position then resolve it 17208 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { 17209 lastIdx = startContainer.childNodes.length - 1; 17210 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; 17211 17212 if (startContainer.nodeType == 3) 17213 startOffset = 0; 17214 } 17215 17216 // If index based end position then resolve it 17217 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { 17218 lastIdx = endContainer.childNodes.length - 1; 17219 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; 17220 17221 if (endContainer.nodeType == 3) 17222 endOffset = endContainer.nodeValue.length; 17223 } 17224 17225 // Expands the node to the closes contentEditable false element if it exists 17226 function findParentContentEditable(node) { 17227 var parent = node; 17228 17229 while (parent) { 17230 if (parent.nodeType === 1 && getContentEditable(parent)) { 17231 return getContentEditable(parent) === "false" ? parent : node; 17232 } 17233 17234 parent = parent.parentNode; 17235 } 17236 17237 return node; 17238 }; 17239 17240 function findWordEndPoint(container, offset, start) { 17241 var walker, node, pos, lastTextNode; 17242 17243 function findSpace(node, offset) { 17244 var pos, pos2, str = node.nodeValue; 17245 17246 if (typeof(offset) == "undefined") { 17247 offset = start ? str.length : 0; 17248 } 17249 17250 if (start) { 17251 pos = str.lastIndexOf(' ', offset); 17252 pos2 = str.lastIndexOf('\u00a0', offset); 17253 pos = pos > pos2 ? pos : pos2; 17254 17255 // Include the space on remove to avoid tag soup 17256 if (pos !== -1 && !remove) { 17257 pos++; 17258 } 17259 } else { 17260 pos = str.indexOf(' ', offset); 17261 pos2 = str.indexOf('\u00a0', offset); 17262 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; 17263 } 17264 17265 return pos; 17266 }; 17267 17268 if (container.nodeType === 3) { 17269 pos = findSpace(container, offset); 17270 17271 if (pos !== -1) { 17272 return {container : container, offset : pos}; 17273 } 17274 17275 lastTextNode = container; 17276 } 17277 17278 // Walk the nodes inside the block 17279 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); 17280 while (node = walker[start ? 'prev' : 'next']()) { 17281 if (node.nodeType === 3) { 17282 lastTextNode = node; 17283 pos = findSpace(node); 17284 17285 if (pos !== -1) { 17286 return {container : node, offset : pos}; 17287 } 17288 } else if (isBlock(node)) { 17289 break; 17290 } 17291 } 17292 17293 if (lastTextNode) { 17294 if (start) { 17295 offset = 0; 17296 } else { 17297 offset = lastTextNode.length; 17298 } 17299 17300 return {container: lastTextNode, offset: offset}; 17301 } 17302 }; 17303 17304 function findSelectorEndPoint(container, sibling_name) { 17305 var parents, i, y, curFormat; 17306 17307 if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) 17308 container = container[sibling_name]; 17309 17310 parents = getParents(container); 17311 for (i = 0; i < parents.length; i++) { 17312 for (y = 0; y < format.length; y++) { 17313 curFormat = format[y]; 17314 17315 // If collapsed state is set then skip formats that doesn't match that 17316 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) 17317 continue; 17318 17319 if (dom.is(parents[i], curFormat.selector)) 17320 return parents[i]; 17321 } 17322 } 17323 17324 return container; 17325 }; 17326 17327 function findBlockEndPoint(container, sibling_name, sibling_name2) { 17328 var node; 17329 17330 // Expand to block of similar type 17331 if (!format[0].wrapper) 17332 node = dom.getParent(container, format[0].block); 17333 17334 // Expand to first wrappable block element or any block element 17335 if (!node) 17336 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock); 17337 17338 // Exclude inner lists from wrapping 17339 if (node && format[0].wrapper) 17340 node = getParents(node, 'ul,ol').reverse()[0] || node; 17341 17342 // Didn't find a block element look for first/last wrappable element 17343 if (!node) { 17344 node = container; 17345 17346 while (node[sibling_name] && !isBlock(node[sibling_name])) { 17347 node = node[sibling_name]; 17348 17349 // Break on BR but include it will be removed later on 17350 // we can't remove it now since we need to check if it can be wrapped 17351 if (isEq(node, 'br')) 17352 break; 17353 } 17354 } 17355 17356 return node || container; 17357 }; 17358 17359 // Expand to closest contentEditable element 17360 startContainer = findParentContentEditable(startContainer); 17361 endContainer = findParentContentEditable(endContainer); 17362 17363 // Exclude bookmark nodes if possible 17364 if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { 17365 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; 17366 startContainer = startContainer.nextSibling || startContainer; 17367 17368 if (startContainer.nodeType == 3) 17369 startOffset = 0; 17370 } 17371 17372 if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { 17373 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; 17374 endContainer = endContainer.previousSibling || endContainer; 17375 17376 if (endContainer.nodeType == 3) 17377 endOffset = endContainer.length; 17378 } 17379 17380 if (format[0].inline) { 17381 if (rng.collapsed) { 17382 // Expand left to closest word boundery 17383 endPoint = findWordEndPoint(startContainer, startOffset, true); 17384 if (endPoint) { 17385 startContainer = endPoint.container; 17386 startOffset = endPoint.offset; 17387 } 17388 17389 // Expand right to closest word boundery 17390 endPoint = findWordEndPoint(endContainer, endOffset); 17391 if (endPoint) { 17392 endContainer = endPoint.container; 17393 endOffset = endPoint.offset; 17394 } 17395 } 17396 17397 // Avoid applying formatting to a trailing space. 17398 leaf = findLeaf(endContainer, endOffset); 17399 if (leaf.node) { 17400 while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) 17401 leaf = findLeaf(leaf.node.previousSibling); 17402 17403 if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && 17404 leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { 17405 17406 if (leaf.offset > 1) { 17407 endContainer = leaf.node; 17408 endContainer.splitText(leaf.offset - 1); 17409 } 17410 } 17411 } 17412 } 17413 17414 // Move start/end point up the tree if the leaves are sharp and if we are in different containers 17415 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>! 17416 // This will reduce the number of wrapper elements that needs to be created 17417 // Move start point up the tree 17418 if (format[0].inline || format[0].block_expand) { 17419 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { 17420 startContainer = findParentContainer(true); 17421 } 17422 17423 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { 17424 endContainer = findParentContainer(); 17425 } 17426 } 17427 17428 // Expand start/end container to matching selector 17429 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { 17430 // Find new startContainer/endContainer if there is better one 17431 startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); 17432 endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); 17433 } 17434 17435 // Expand start/end container to matching block element or text node 17436 if (format[0].block || format[0].selector) { 17437 // Find new startContainer/endContainer if there is better one 17438 startContainer = findBlockEndPoint(startContainer, 'previousSibling'); 17439 endContainer = findBlockEndPoint(endContainer, 'nextSibling'); 17440 17441 // Non block element then try to expand up the leaf 17442 if (format[0].block) { 17443 if (!isBlock(startContainer)) 17444 startContainer = findParentContainer(true); 17445 17446 if (!isBlock(endContainer)) 17447 endContainer = findParentContainer(); 17448 } 17449 } 17450 17451 // Setup index for startContainer 17452 if (startContainer.nodeType == 1) { 17453 startOffset = nodeIndex(startContainer); 17454 startContainer = startContainer.parentNode; 17455 } 17456 17457 // Setup index for endContainer 17458 if (endContainer.nodeType == 1) { 17459 endOffset = nodeIndex(endContainer) + 1; 17460 endContainer = endContainer.parentNode; 17461 } 17462 17463 // Return new range like object 17464 return { 17465 startContainer : startContainer, 17466 startOffset : startOffset, 17467 endContainer : endContainer, 17468 endOffset : endOffset 17469 }; 17470 } 17471 17472 function removeFormat(format, vars, node, compare_node) { 17473 var i, attrs, stylesModified; 17474 17475 // Check if node matches format 17476 if (!matchName(node, format)) 17477 return FALSE; 17478 17479 // Should we compare with format attribs and styles 17480 if (format.remove != 'all') { 17481 // Remove styles 17482 each(format.styles, function(value, name) { 17483 value = replaceVars(value, vars); 17484 17485 // Indexed array 17486 if (typeof(name) === 'number') { 17487 name = value; 17488 compare_node = 0; 17489 } 17490 17491 if (!compare_node || isEq(getStyle(compare_node, name), value)) 17492 dom.setStyle(node, name, ''); 17493 17494 stylesModified = 1; 17495 }); 17496 17497 // Remove style attribute if it's empty 17498 if (stylesModified && dom.getAttrib(node, 'style') == '') { 17499 node.removeAttribute('style'); 17500 node.removeAttribute('data-mce-style'); 17501 } 17502 17503 // Remove attributes 17504 each(format.attributes, function(value, name) { 17505 var valueOut; 17506 17507 value = replaceVars(value, vars); 17508 17509 // Indexed array 17510 if (typeof(name) === 'number') { 17511 name = value; 17512 compare_node = 0; 17513 } 17514 17515 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { 17516 // Keep internal classes 17517 if (name == 'class') { 17518 value = dom.getAttrib(node, name); 17519 if (value) { 17520 // Build new class value where everything is removed except the internal prefixed classes 17521 valueOut = ''; 17522 each(value.split(/\s+/), function(cls) { 17523 if (/mce\w+/.test(cls)) 17524 valueOut += (valueOut ? ' ' : '') + cls; 17525 }); 17526 17527 // We got some internal classes left 17528 if (valueOut) { 17529 dom.setAttrib(node, name, valueOut); 17530 return; 17531 } 17532 } 17533 } 17534 17535 // IE6 has a bug where the attribute doesn't get removed correctly 17536 if (name == "class") 17537 node.removeAttribute('className'); 17538 17539 // Remove mce prefixed attributes 17540 if (MCE_ATTR_RE.test(name)) 17541 node.removeAttribute('data-mce-' + name); 17542 17543 node.removeAttribute(name); 17544 } 17545 }); 17546 17547 // Remove classes 17548 each(format.classes, function(value) { 17549 value = replaceVars(value, vars); 17550 17551 if (!compare_node || dom.hasClass(compare_node, value)) 17552 dom.removeClass(node, value); 17553 }); 17554 17555 // Check for non internal attributes 17556 attrs = dom.getAttribs(node); 17557 for (i = 0; i < attrs.length; i++) { 17558 if (attrs[i].nodeName.indexOf('_') !== 0) 17559 return FALSE; 17560 } 17561 } 17562 17563 // Remove the inline child if it's empty for example <b> or <span> 17564 if (format.remove != 'none') { 17565 removeNode(node, format); 17566 return TRUE; 17567 } 17568 }; 17569 17570 function removeNode(node, format) { 17571 var parentNode = node.parentNode, rootBlockElm; 17572 17573 function find(node, next, inc) { 17574 node = getNonWhiteSpaceSibling(node, next, inc); 17575 17576 return !node || (node.nodeName == 'BR' || isBlock(node)); 17577 }; 17578 17579 if (format.block) { 17580 if (!forcedRootBlock) { 17581 // Append BR elements if needed before we remove the block 17582 if (isBlock(node) && !isBlock(parentNode)) { 17583 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) 17584 node.insertBefore(dom.create('br'), node.firstChild); 17585 17586 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) 17587 node.appendChild(dom.create('br')); 17588 } 17589 } else { 17590 // Wrap the block in a forcedRootBlock if we are at the root of document 17591 if (parentNode == dom.getRoot()) { 17592 if (!format.list_block || !isEq(node, format.list_block)) { 17593 each(tinymce.grep(node.childNodes), function(node) { 17594 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { 17595 if (!rootBlockElm) 17596 rootBlockElm = wrap(node, forcedRootBlock); 17597 else 17598 rootBlockElm.appendChild(node); 17599 } else 17600 rootBlockElm = 0; 17601 }); 17602 } 17603 } 17604 } 17605 } 17606 17607 // Never remove nodes that isn't the specified inline element if a selector is specified too 17608 if (format.selector && format.inline && !isEq(format.inline, node)) 17609 return; 17610 17611 dom.remove(node, 1); 17612 }; 17613 17614 function getNonWhiteSpaceSibling(node, next, inc) { 17615 if (node) { 17616 next = next ? 'nextSibling' : 'previousSibling'; 17617 17618 for (node = inc ? node : node[next]; node; node = node[next]) { 17619 if (node.nodeType == 1 || !isWhiteSpaceNode(node)) 17620 return node; 17621 } 17622 } 17623 }; 17624 17625 function isBookmarkNode(node) { 17626 return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; 17627 }; 17628 17629 function mergeSiblings(prev, next) { 17630 var marker, sibling, tmpSibling; 17631 17632 function compareElements(node1, node2) { 17633 // Not the same name 17634 if (node1.nodeName != node2.nodeName) 17635 return FALSE; 17636 17637 function getAttribs(node) { 17638 var attribs = {}; 17639 17640 each(dom.getAttribs(node), function(attr) { 17641 var name = attr.nodeName.toLowerCase(); 17642 17643 // Don't compare internal attributes or style 17644 if (name.indexOf('_') !== 0 && name !== 'style') 17645 attribs[name] = dom.getAttrib(node, name); 17646 }); 17647 17648 return attribs; 17649 }; 17650 17651 function compareObjects(obj1, obj2) { 17652 var value, name; 17653 17654 for (name in obj1) { 17655 // Obj1 has item obj2 doesn't have 17656 if (obj1.hasOwnProperty(name)) { 17657 value = obj2[name]; 17658 17659 // Obj2 doesn't have obj1 item 17660 if (value === undef) 17661 return FALSE; 17662 17663 // Obj2 item has a different value 17664 if (obj1[name] != value) 17665 return FALSE; 17666 17667 // Delete similar value 17668 delete obj2[name]; 17669 } 17670 } 17671 17672 // Check if obj 2 has something obj 1 doesn't have 17673 for (name in obj2) { 17674 // Obj2 has item obj1 doesn't have 17675 if (obj2.hasOwnProperty(name)) 17676 return FALSE; 17677 } 17678 17679 return TRUE; 17680 }; 17681 17682 // Attribs are not the same 17683 if (!compareObjects(getAttribs(node1), getAttribs(node2))) 17684 return FALSE; 17685 17686 // Styles are not the same 17687 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) 17688 return FALSE; 17689 17690 return TRUE; 17691 }; 17692 17693 function findElementSibling(node, sibling_name) { 17694 for (sibling = node; sibling; sibling = sibling[sibling_name]) { 17695 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) 17696 return node; 17697 17698 if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) 17699 return sibling; 17700 } 17701 17702 return node; 17703 }; 17704 17705 // Check if next/prev exists and that they are elements 17706 if (prev && next) { 17707 // If previous sibling is empty then jump over it 17708 prev = findElementSibling(prev, 'previousSibling'); 17709 next = findElementSibling(next, 'nextSibling'); 17710 17711 // Compare next and previous nodes 17712 if (compareElements(prev, next)) { 17713 // Append nodes between 17714 for (sibling = prev.nextSibling; sibling && sibling != next;) { 17715 tmpSibling = sibling; 17716 sibling = sibling.nextSibling; 17717 prev.appendChild(tmpSibling); 17718 } 17719 17720 // Remove next node 17721 dom.remove(next); 17722 17723 // Move children into prev node 17724 each(tinymce.grep(next.childNodes), function(node) { 17725 prev.appendChild(node); 17726 }); 17727 17728 return prev; 17729 } 17730 } 17731 17732 return next; 17733 }; 17734 17735 function isTextBlock(name) { 17736 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name); 17737 }; 17738 17739 function getContainer(rng, start) { 17740 var container, offset, lastIdx, walker; 17741 17742 container = rng[start ? 'startContainer' : 'endContainer']; 17743 offset = rng[start ? 'startOffset' : 'endOffset']; 17744 17745 if (container.nodeType == 1) { 17746 lastIdx = container.childNodes.length - 1; 17747 17748 if (!start && offset) 17749 offset--; 17750 17751 container = container.childNodes[offset > lastIdx ? lastIdx : offset]; 17752 } 17753 17754 // If start text node is excluded then walk to the next node 17755 if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { 17756 container = new TreeWalker(container, ed.getBody()).next() || container; 17757 } 17758 17759 // If end text node is excluded then walk to the previous node 17760 if (container.nodeType === 3 && !start && offset === 0) { 17761 container = new TreeWalker(container, ed.getBody()).prev() || container; 17762 } 17763 17764 return container; 17765 }; 17766 17767 function performCaretAction(type, name, vars) { 17768 var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; 17769 17770 // Creates a caret container bogus element 17771 function createCaretContainer(fill) { 17772 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); 17773 17774 if (fill) { 17775 caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR)); 17776 } 17777 17778 return caretContainer; 17779 }; 17780 17781 function isCaretContainerEmpty(node, nodes) { 17782 while (node) { 17783 if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) { 17784 return false; 17785 } 17786 17787 // Collect nodes 17788 if (nodes && node.nodeType === 1) { 17789 nodes.push(node); 17790 } 17791 17792 node = node.firstChild; 17793 } 17794 17795 return true; 17796 }; 17797 17798 // Returns any parent caret container element 17799 function getParentCaretContainer(node) { 17800 while (node) { 17801 if (node.id === caretContainerId) { 17802 return node; 17803 } 17804 17805 node = node.parentNode; 17806 } 17807 }; 17808 17809 // Finds the first text node in the specified node 17810 function findFirstTextNode(node) { 17811 var walker; 17812 17813 if (node) { 17814 walker = new TreeWalker(node, node); 17815 17816 for (node = walker.current(); node; node = walker.next()) { 17817 if (node.nodeType === 3) { 17818 return node; 17819 } 17820 } 17821 } 17822 }; 17823 17824 // Removes the caret container for the specified node or all on the current document 17825 function removeCaretContainer(node, move_caret) { 17826 var child, rng; 17827 17828 if (!node) { 17829 node = getParentCaretContainer(selection.getStart()); 17830 17831 if (!node) { 17832 while (node = dom.get(caretContainerId)) { 17833 removeCaretContainer(node, false); 17834 } 17835 } 17836 } else { 17837 rng = selection.getRng(true); 17838 17839 if (isCaretContainerEmpty(node)) { 17840 if (move_caret !== false) { 17841 rng.setStartBefore(node); 17842 rng.setEndBefore(node); 17843 } 17844 17845 dom.remove(node); 17846 } else { 17847 child = findFirstTextNode(node); 17848 17849 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) { 17850 child = child.deleteData(0, 1); 17851 } 17852 17853 dom.remove(node, 1); 17854 } 17855 17856 selection.setRng(rng); 17857 } 17858 }; 17859 17860 // Applies formatting to the caret postion 17861 function applyCaretFormat() { 17862 var rng, caretContainer, textNode, offset, bookmark, container, text; 17863 17864 rng = selection.getRng(true); 17865 offset = rng.startOffset; 17866 container = rng.startContainer; 17867 text = container.nodeValue; 17868 17869 caretContainer = getParentCaretContainer(selection.getStart()); 17870 if (caretContainer) { 17871 textNode = findFirstTextNode(caretContainer); 17872 } 17873 17874 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character 17875 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { 17876 // Get bookmark of caret position 17877 bookmark = selection.getBookmark(); 17878 17879 // Collapse bookmark range (WebKit) 17880 rng.collapse(true); 17881 17882 // Expand the range to the closest word and split it at those points 17883 rng = expandRng(rng, get(name)); 17884 rng = rangeUtils.split(rng); 17885 17886 // Apply the format to the range 17887 apply(name, vars, rng); 17888 17889 // Move selection back to caret position 17890 selection.moveToBookmark(bookmark); 17891 } else { 17892 if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) { 17893 caretContainer = createCaretContainer(true); 17894 textNode = caretContainer.firstChild; 17895 17896 rng.insertNode(caretContainer); 17897 offset = 1; 17898 17899 apply(name, vars, caretContainer); 17900 } else { 17901 apply(name, vars, caretContainer); 17902 } 17903 17904 // Move selection to text node 17905 selection.setCursorLocation(textNode, offset); 17906 } 17907 }; 17908 17909 function removeCaretFormat() { 17910 var rng = selection.getRng(true), container, offset, bookmark, 17911 hasContentAfter, node, formatNode, parents = [], i, caretContainer; 17912 17913 container = rng.startContainer; 17914 offset = rng.startOffset; 17915 node = container; 17916 17917 if (container.nodeType == 3) { 17918 if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) { 17919 hasContentAfter = true; 17920 } 17921 17922 node = node.parentNode; 17923 } 17924 17925 while (node) { 17926 if (matchNode(node, name, vars)) { 17927 formatNode = node; 17928 break; 17929 } 17930 17931 if (node.nextSibling) { 17932 hasContentAfter = true; 17933 } 17934 17935 parents.push(node); 17936 node = node.parentNode; 17937 } 17938 17939 // Node doesn't have the specified format 17940 if (!formatNode) { 17941 return; 17942 } 17943 17944 // Is there contents after the caret then remove the format on the element 17945 if (hasContentAfter) { 17946 // Get bookmark of caret position 17947 bookmark = selection.getBookmark(); 17948 17949 // Collapse bookmark range (WebKit) 17950 rng.collapse(true); 17951 17952 // Expand the range to the closest word and split it at those points 17953 rng = expandRng(rng, get(name), true); 17954 rng = rangeUtils.split(rng); 17955 17956 // Remove the format from the range 17957 remove(name, vars, rng); 17958 17959 // Move selection back to caret position 17960 selection.moveToBookmark(bookmark); 17961 } else { 17962 caretContainer = createCaretContainer(); 17963 17964 node = caretContainer; 17965 for (i = parents.length - 1; i >= 0; i--) { 17966 node.appendChild(dom.clone(parents[i], false)); 17967 node = node.firstChild; 17968 } 17969 17970 // Insert invisible character into inner most format element 17971 node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR)); 17972 node = node.firstChild; 17973 17974 // Insert caret container after the formated node 17975 dom.insertAfter(caretContainer, formatNode); 17976 17977 // Move selection to text node 17978 selection.setCursorLocation(node, 1); 17979 } 17980 }; 17981 17982 // Checks if the parent caret container node isn't empty if that is the case it 17983 // will remove the bogus state on all children that isn't empty 17984 function unmarkBogusCaretParents() { 17985 var i, caretContainer, node; 17986 17987 caretContainer = getParentCaretContainer(selection.getStart()); 17988 if (caretContainer && !dom.isEmpty(caretContainer)) { 17989 tinymce.walk(caretContainer, function(node) { 17990 if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { 17991 dom.setAttrib(node, 'data-mce-bogus', null); 17992 } 17993 }, 'childNodes'); 17994 } 17995 }; 17996 17997 // Only bind the caret events once 17998 if (!self._hasCaretEvents) { 17999 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements 18000 ed.onBeforeGetContent.addToTop(function() { 18001 var nodes = [], i; 18002 18003 if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { 18004 // Mark children 18005 i = nodes.length; 18006 while (i--) { 18007 dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); 18008 } 18009 } 18010 }); 18011 18012 // Remove caret container on mouse up and on key up 18013 tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) { 18014 ed[name].addToTop(function() { 18015 removeCaretContainer(); 18016 unmarkBogusCaretParents(); 18017 }); 18018 }); 18019 18020 // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys 18021 ed.onKeyDown.addToTop(function(ed, e) { 18022 var keyCode = e.keyCode; 18023 18024 if (keyCode == 8 || keyCode == 37 || keyCode == 39) { 18025 removeCaretContainer(getParentCaretContainer(selection.getStart())); 18026 } 18027 18028 unmarkBogusCaretParents(); 18029 }); 18030 18031 // Remove bogus state if they got filled by contents using editor.selection.setContent 18032 selection.onSetContent.add(unmarkBogusCaretParents); 18033 18034 self._hasCaretEvents = true; 18035 } 18036 18037 // Do apply or remove caret format 18038 if (type == "apply") { 18039 applyCaretFormat(); 18040 } else { 18041 removeCaretFormat(); 18042 } 18043 }; 18044 18045 function moveStart(rng) { 18046 var container = rng.startContainer, 18047 offset = rng.startOffset, isAtEndOfText, 18048 walker, node, nodes, tmpNode; 18049 18050 // Convert text node into index if possible 18051 if (container.nodeType == 3 && offset >= container.nodeValue.length) { 18052 // Get the parent container location and walk from there 18053 offset = nodeIndex(container); 18054 container = container.parentNode; 18055 isAtEndOfText = true; 18056 } 18057 18058 // Move startContainer/startOffset in to a suitable node 18059 if (container.nodeType == 1) { 18060 nodes = container.childNodes; 18061 container = nodes[Math.min(offset, nodes.length - 1)]; 18062 walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); 18063 18064 // If offset is at end of the parent node walk to the next one 18065 if (offset > nodes.length - 1 || isAtEndOfText) 18066 walker.next(); 18067 18068 for (node = walker.current(); node; node = walker.next()) { 18069 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { 18070 // IE has a "neat" feature where it moves the start node into the closest element 18071 // we can avoid this by inserting an element before it and then remove it after we set the selection 18072 tmpNode = dom.create('a', null, INVISIBLE_CHAR); 18073 node.parentNode.insertBefore(tmpNode, node); 18074 18075 // Set selection and remove tmpNode 18076 rng.setStart(node, 0); 18077 selection.setRng(rng); 18078 dom.remove(tmpNode); 18079 18080 return; 18081 } 18082 } 18083 } 18084 }; 18085 }; 18086 })(tinymce); 18087 18088 tinymce.onAddEditor.add(function(tinymce, ed) { 18089 var filters, fontSizes, dom, settings = ed.settings; 18090 18091 function replaceWithSpan(node, styles) { 18092 tinymce.each(styles, function(value, name) { 18093 if (value) 18094 dom.setStyle(node, name, value); 18095 }); 18096 18097 dom.rename(node, 'span'); 18098 }; 18099 18100 function convert(editor, params) { 18101 dom = editor.dom; 18102 18103 if (settings.convert_fonts_to_spans) { 18104 tinymce.each(dom.select('font,u,strike', params.node), function(node) { 18105 filters[node.nodeName.toLowerCase()](ed.dom, node); 18106 }); 18107 } 18108 }; 18109 18110 if (settings.inline_styles) { 18111 fontSizes = tinymce.explode(settings.font_size_legacy_values); 18112 18113 filters = { 18114 font : function(dom, node) { 18115 replaceWithSpan(node, { 18116 backgroundColor : node.style.backgroundColor, 18117 color : node.color, 18118 fontFamily : node.face, 18119 fontSize : fontSizes[parseInt(node.size, 10) - 1] 18120 }); 18121 }, 18122 18123 u : function(dom, node) { 18124 replaceWithSpan(node, { 18125 textDecoration : 'underline' 18126 }); 18127 }, 18128 18129 strike : function(dom, node) { 18130 replaceWithSpan(node, { 18131 textDecoration : 'line-through' 18132 }); 18133 } 18134 }; 18135 18136 ed.onPreProcess.add(convert); 18137 ed.onSetContent.add(convert); 18138 18139 ed.onInit.add(function() { 18140 ed.selection.onSetContent.add(convert); 18141 }); 18142 } 18143 }); 18144 18145 (function(tinymce) { 18146 var TreeWalker = tinymce.dom.TreeWalker; 18147 18148 tinymce.EnterKey = function(editor) { 18149 var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements(); 18150 18151 function handleEnterKey(evt) { 18152 var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, 18153 newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; 18154 18155 // Returns true if the block can be split into two blocks or not 18156 function canSplitBlock(node) { 18157 return node && 18158 dom.isBlock(node) && 18159 !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && 18160 !/^(fixed|absolute)/i.test(node.style.position) && 18161 dom.getContentEditable(node) !== "true"; 18162 }; 18163 18164 // Renders empty block on IE 18165 function renderBlockOnIE(block) { 18166 var oldRng; 18167 18168 if (tinymce.isIE && dom.isBlock(block)) { 18169 oldRng = selection.getRng(); 18170 block.appendChild(dom.create('span', null, '\u00a0')); 18171 selection.select(block); 18172 block.lastChild.outerHTML = ''; 18173 selection.setRng(oldRng); 18174 } 18175 }; 18176 18177 // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p> 18178 function trimInlineElementsOnLeftSideOfBlock(block) { 18179 var node = block, firstChilds = [], i; 18180 18181 // Find inner most first child ex: <p><i><b>*</b></i></p> 18182 while (node = node.firstChild) { 18183 if (dom.isBlock(node)) { 18184 return; 18185 } 18186 18187 if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18188 firstChilds.push(node); 18189 } 18190 } 18191 18192 i = firstChilds.length; 18193 while (i--) { 18194 node = firstChilds[i]; 18195 if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { 18196 dom.remove(node); 18197 } 18198 } 18199 }; 18200 18201 // Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image 18202 function moveToCaretPosition(root) { 18203 var walker, node, rng, y, viewPort, lastNode = root, tempElm; 18204 18205 rng = dom.createRng(); 18206 18207 if (root.hasChildNodes()) { 18208 walker = new TreeWalker(root, root); 18209 18210 while (node = walker.current()) { 18211 if (node.nodeType == 3) { 18212 rng.setStart(node, 0); 18213 rng.setEnd(node, 0); 18214 break; 18215 } 18216 18217 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18218 rng.setStartBefore(node); 18219 rng.setEndBefore(node); 18220 break; 18221 } 18222 18223 lastNode = node; 18224 node = walker.next(); 18225 } 18226 18227 if (!node) { 18228 rng.setStart(lastNode, 0); 18229 rng.setEnd(lastNode, 0); 18230 } 18231 } else { 18232 if (root.nodeName == 'BR') { 18233 if (root.nextSibling && dom.isBlock(root.nextSibling)) { 18234 // Trick on older IE versions to render the caret before the BR between two lists 18235 if (!documentMode || documentMode < 9) { 18236 tempElm = dom.create('br'); 18237 root.parentNode.insertBefore(tempElm, root); 18238 } 18239 18240 rng.setStartBefore(root); 18241 rng.setEndBefore(root); 18242 } else { 18243 rng.setStartAfter(root); 18244 rng.setEndAfter(root); 18245 } 18246 } else { 18247 rng.setStart(root, 0); 18248 rng.setEnd(root, 0); 18249 } 18250 } 18251 18252 selection.setRng(rng); 18253 18254 // Remove tempElm created for old IE:s 18255 dom.remove(tempElm); 18256 18257 viewPort = dom.getViewPort(editor.getWin()); 18258 18259 // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs 18260 y = dom.getPos(root).y; 18261 if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) { 18262 editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks 18263 } 18264 }; 18265 18266 // Creates a new block element by cloning the current one or creating a new one if the name is specified 18267 // This function will also copy any text formatting from the parent block and add it to the new one 18268 function createNewBlock(name) { 18269 var node = container, block, clonedNode, caretNode; 18270 18271 block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false); 18272 caretNode = block; 18273 18274 // Clone any parent styles 18275 if (settings.keep_styles !== false) { 18276 do { 18277 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { 18278 clonedNode = node.cloneNode(false); 18279 dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique 18280 18281 if (block.hasChildNodes()) { 18282 clonedNode.appendChild(block.firstChild); 18283 block.appendChild(clonedNode); 18284 } else { 18285 caretNode = clonedNode; 18286 block.appendChild(clonedNode); 18287 } 18288 } 18289 } while (node = node.parentNode); 18290 } 18291 18292 // BR is needed in empty blocks on non IE browsers 18293 if (!tinymce.isIE) { 18294 caretNode.innerHTML = '<br>'; 18295 } 18296 18297 return block; 18298 }; 18299 18300 // Returns true/false if the caret is at the start/end of the parent block element 18301 function isCaretAtStartOrEndOfBlock(start) { 18302 var walker, node, name; 18303 18304 // Caret is in the middle of a text node like "a|b" 18305 if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) { 18306 return false; 18307 } 18308 18309 // If after the last element in block node edge case for #5091 18310 if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) { 18311 return true; 18312 } 18313 18314 // If the caret if before the first element in parentBlock 18315 if (start && container.nodeType == 1 && container == parentBlock.firstChild) { 18316 return true; 18317 } 18318 18319 // Caret can be before/after a table 18320 if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) { 18321 return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start); 18322 } 18323 18324 // Walk the DOM and look for text nodes or non empty elements 18325 walker = new TreeWalker(container, parentBlock); 18326 18327 // If caret is in beginning or end of a text block then jump to the next/previous node 18328 if (container.nodeType == 3) { 18329 if (start && offset == 0) { 18330 walker.prev(); 18331 } else if (!start && offset == container.nodeValue.length) { 18332 walker.next(); 18333 } 18334 } 18335 18336 while (node = walker.current()) { 18337 if (node.nodeType === 1) { 18338 // Ignore bogus elements 18339 if (!node.getAttribute('data-mce-bogus')) { 18340 // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p> 18341 name = node.nodeName.toLowerCase(); 18342 if (nonEmptyElementsMap[name] && name !== 'br') { 18343 return false; 18344 } 18345 } 18346 } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) { 18347 return false; 18348 } 18349 18350 if (start) { 18351 walker.prev(); 18352 } else { 18353 walker.next(); 18354 } 18355 } 18356 18357 return true; 18358 }; 18359 18360 // Wraps any text nodes or inline elements in the specified forced root block name 18361 function wrapSelfAndSiblingsInDefaultBlock(container, offset) { 18362 var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P'; 18363 18364 // Not in a block element or in a table cell or caption 18365 parentBlock = dom.getParent(container, dom.isBlock); 18366 if (!parentBlock || !canSplitBlock(parentBlock)) { 18367 parentBlock = parentBlock || editableRoot; 18368 18369 if (!parentBlock.hasChildNodes()) { 18370 newBlock = dom.create(blockName); 18371 parentBlock.appendChild(newBlock); 18372 rng.setStart(newBlock, 0); 18373 rng.setEnd(newBlock, 0); 18374 return newBlock; 18375 } 18376 18377 // Find parent that is the first child of parentBlock 18378 node = container; 18379 while (node.parentNode != parentBlock) { 18380 node = node.parentNode; 18381 } 18382 18383 // Loop left to find start node start wrapping at 18384 while (node && !dom.isBlock(node)) { 18385 startNode = node; 18386 node = node.previousSibling; 18387 } 18388 18389 if (startNode) { 18390 newBlock = dom.create(blockName); 18391 startNode.parentNode.insertBefore(newBlock, startNode); 18392 18393 // Start wrapping until we hit a block 18394 node = startNode; 18395 while (node && !dom.isBlock(node)) { 18396 next = node.nextSibling; 18397 newBlock.appendChild(node); 18398 node = next; 18399 } 18400 18401 // Restore range to it's past location 18402 rng.setStart(container, offset); 18403 rng.setEnd(container, offset); 18404 } 18405 } 18406 18407 return container; 18408 }; 18409 18410 // Inserts a block or br before/after or in the middle of a split list of the LI is empty 18411 function handleEmptyListItem() { 18412 function isFirstOrLastLi(first) { 18413 var node = containerBlock[first ? 'firstChild' : 'lastChild']; 18414 18415 // Find first/last element since there might be whitespace there 18416 while (node) { 18417 if (node.nodeType == 1) { 18418 break; 18419 } 18420 18421 node = node[first ? 'nextSibling' : 'previousSibling']; 18422 } 18423 18424 return node === parentBlock; 18425 }; 18426 18427 newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR'); 18428 18429 if (isFirstOrLastLi(true) && isFirstOrLastLi()) { 18430 // Is first and last list item then replace the OL/UL with a text block 18431 dom.replace(newBlock, containerBlock); 18432 } else if (isFirstOrLastLi(true)) { 18433 // First LI in list then remove LI and add text block before list 18434 containerBlock.parentNode.insertBefore(newBlock, containerBlock); 18435 } else if (isFirstOrLastLi()) { 18436 // Last LI in list then temove LI and add text block after list 18437 dom.insertAfter(newBlock, containerBlock); 18438 renderBlockOnIE(newBlock); 18439 } else { 18440 // Middle LI in list the split the list and insert a text block in the middle 18441 // Extract after fragment and insert it after the current block 18442 tmpRng = rng.cloneRange(); 18443 tmpRng.setStartAfter(parentBlock); 18444 tmpRng.setEndAfter(containerBlock); 18445 fragment = tmpRng.extractContents(); 18446 dom.insertAfter(fragment, containerBlock); 18447 dom.insertAfter(newBlock, containerBlock); 18448 } 18449 18450 dom.remove(parentBlock); 18451 moveToCaretPosition(newBlock); 18452 undoManager.add(); 18453 }; 18454 18455 // Walks the parent block to the right and look for BR elements 18456 function hasRightSideBr() { 18457 var walker = new TreeWalker(container, parentBlock), node; 18458 18459 while (node = walker.current()) { 18460 if (node.nodeName == 'BR') { 18461 return true; 18462 } 18463 18464 node = walker.next(); 18465 } 18466 } 18467 18468 // Inserts a BR element if the forced_root_block option is set to false or empty string 18469 function insertBr() { 18470 var brElm, extraBr; 18471 18472 if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { 18473 // Insert extra BR element at the end block elements 18474 if (!tinymce.isIE && !hasRightSideBr()) { 18475 brElm = dom.create('br') 18476 rng.insertNode(brElm); 18477 rng.setStartAfter(brElm); 18478 rng.setEndAfter(brElm); 18479 extraBr = true; 18480 } 18481 } 18482 18483 brElm = dom.create('br'); 18484 rng.insertNode(brElm); 18485 18486 // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it 18487 if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { 18488 brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); 18489 } 18490 18491 if (!extraBr) { 18492 rng.setStartAfter(brElm); 18493 rng.setEndAfter(brElm); 18494 } else { 18495 rng.setStartBefore(brElm); 18496 rng.setEndBefore(brElm); 18497 } 18498 18499 selection.setRng(rng); 18500 undoManager.add(); 18501 }; 18502 18503 // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element 18504 function trimLeadingLineBreaks(node) { 18505 do { 18506 if (node.nodeType === 3) { 18507 node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, ''); 18508 } 18509 18510 node = node.firstChild; 18511 } while (node); 18512 }; 18513 18514 function getEditableRoot(node) { 18515 var root = dom.getRoot(), parent, editableRoot; 18516 18517 // Get all parents until we hit a non editable parent or the root 18518 parent = node; 18519 while (parent !== root && dom.getContentEditable(parent) !== "false") { 18520 if (dom.getContentEditable(parent) === "true") { 18521 editableRoot = parent; 18522 } 18523 18524 parent = parent.parentNode; 18525 } 18526 18527 return parent !== root ? editableRoot : root; 18528 }; 18529 18530 // Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block 18531 function addBrToBlockIfNeeded(block) { 18532 var lastChild; 18533 18534 // IE will render the blocks correctly other browsers needs a BR 18535 if (!tinymce.isIE) { 18536 block.normalize(); // Remove empty text nodes that got left behind by the extract 18537 18538 // Check if the block is empty or contains a floated last child 18539 lastChild = block.lastChild; 18540 if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) { 18541 dom.add(block, 'br'); 18542 } 18543 } 18544 }; 18545 18546 // Delete any selected contents 18547 if (!rng.collapsed) { 18548 editor.execCommand('Delete'); 18549 return; 18550 } 18551 18552 // Event is blocked by some other handler for example the lists plugin 18553 if (evt.isDefaultPrevented()) { 18554 return; 18555 } 18556 18557 // Setup range items and newBlockName 18558 container = rng.startContainer; 18559 offset = rng.startOffset; 18560 newBlockName = settings.forced_root_block; 18561 newBlockName = newBlockName ? newBlockName.toUpperCase() : ''; 18562 documentMode = dom.doc.documentMode; 18563 18564 // Resolve node index 18565 if (container.nodeType == 1 && container.hasChildNodes()) { 18566 isAfterLastNodeInContainer = offset > container.childNodes.length - 1; 18567 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; 18568 if (isAfterLastNodeInContainer && container.nodeType == 3) { 18569 offset = container.nodeValue.length; 18570 } else { 18571 offset = 0; 18572 } 18573 } 18574 18575 // Get editable root node normaly the body element but sometimes a div or span 18576 editableRoot = getEditableRoot(container); 18577 18578 // If there is no editable root then enter is done inside a contentEditable false element 18579 if (!editableRoot) { 18580 return; 18581 } 18582 18583 undoManager.beforeChange(); 18584 18585 // If editable root isn't block nor the root of the editor 18586 if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) { 18587 if (!newBlockName || evt.shiftKey) { 18588 insertBr(); 18589 } 18590 18591 return; 18592 } 18593 18594 // Wrap the current node and it's sibling in a default block if it's needed. 18595 // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td> 18596 // This won't happen if root blocks are disabled or the shiftKey is pressed 18597 if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) { 18598 container = wrapSelfAndSiblingsInDefaultBlock(container, offset); 18599 } 18600 18601 // Find parent block and setup empty block paddings 18602 parentBlock = dom.getParent(container, dom.isBlock); 18603 containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; 18604 18605 // Setup block names 18606 parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 18607 containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 18608 18609 // Handle enter inside an empty list item 18610 if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) { 18611 // Let the list plugin or browser handle nested lists for now 18612 if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) { 18613 return false; 18614 } 18615 18616 handleEmptyListItem(); 18617 return; 18618 } 18619 18620 // Don't split PRE tags but insert a BR instead easier when writing code samples etc 18621 if (parentBlockName == 'PRE' && settings.br_in_pre !== false) { 18622 if (!evt.shiftKey) { 18623 insertBr(); 18624 return; 18625 } 18626 } else { 18627 // If no root block is configured then insert a BR by default or if the shiftKey is pressed 18628 if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) { 18629 insertBr(); 18630 return; 18631 } 18632 } 18633 18634 // Default block name if it's not configured 18635 newBlockName = newBlockName || 'P'; 18636 18637 // Insert new block before/after the parent block depending on caret location 18638 if (isCaretAtStartOrEndOfBlock()) { 18639 // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup 18640 if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') { 18641 newBlock = createNewBlock(newBlockName); 18642 } else { 18643 newBlock = createNewBlock(); 18644 } 18645 18646 // Split the current container block element if enter is pressed inside an empty inner block element 18647 if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) { 18648 // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P 18649 newBlock = dom.split(containerBlock, parentBlock); 18650 } else { 18651 dom.insertAfter(newBlock, parentBlock); 18652 } 18653 18654 moveToCaretPosition(newBlock); 18655 } else if (isCaretAtStartOrEndOfBlock(true)) { 18656 // Insert new block before 18657 newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock); 18658 renderBlockOnIE(newBlock); 18659 } else { 18660 // Extract after fragment and insert it after the current block 18661 tmpRng = rng.cloneRange(); 18662 tmpRng.setEndAfter(parentBlock); 18663 fragment = tmpRng.extractContents(); 18664 trimLeadingLineBreaks(fragment); 18665 newBlock = fragment.firstChild; 18666 dom.insertAfter(fragment, parentBlock); 18667 trimInlineElementsOnLeftSideOfBlock(newBlock); 18668 addBrToBlockIfNeeded(parentBlock); 18669 moveToCaretPosition(newBlock); 18670 } 18671 18672 dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique 18673 undoManager.add(); 18674 } 18675 18676 editor.onKeyDown.add(function(ed, evt) { 18677 if (evt.keyCode == 13) { 18678 if (handleEnterKey(evt) !== false) { 18679 evt.preventDefault(); 18680 } 18681 } 18682 }); 18683 }; 18684 })(tinymce); 18685 18686